Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
S’applique à :SQL Server
Azure SQL Database
Azure SQL Managed Instance
Cet article fournit les étapes permettant de permuter des clés Always Encrypted à l’aide du module SqlServer PowerShell. Pour plus d’informations sur la façon de commencer à utiliser le module SqlServer PowerShell pour Always Encrypted, consultez Configurer Always Encrypted à l’aide de PowerShell.
Remarque
Microsoft recommande d’utiliser PowerShell 7 ou version ultérieure lors de l’exécution de scripts PowerShell Always Encrypted. PowerShell 7 offre une prise en charge multiplateforme améliorée, de meilleures performances et la dernière compatibilité avec le module SqlServer (v22+), qui est nécessaire pour de nombreux scénarios Always Encrypted.
La rotation des clés Always Encrypted est le processus de remplacement d’une clé existante par une nouvelle clé. Vous devrez peut-être faire pivoter une clé si elle est compromise, ou pour se conformer aux stratégies ou réglementations de conformité de votre organisation qui imposent une rotation régulière des clés de chiffrement.
Always Encrypted utilise deux types de clés, il existe donc deux principaux flux de travail de rotation des clés : la rotation des clés principales de colonnes et la rotation des clés de chiffrement de colonnes.
- Rotation des clés de chiffrement de colonne : implique le déchiffrement des données chiffrées avec la clé actuelle et le rechiffrage des données à l’aide de la nouvelle clé de chiffrement de colonne. La permutation d’une clé de chiffrement de colonne nécessitant l’accès aux clés et à la base de données, la permutation des clés de chiffrement de colonne ne peut être effectuée que sans séparation des rôles.
- Rotation des clés principales de colonne : consiste à déchiffrer les clés de chiffrement de colonne protégées avec la clé principale de colonne actuelle, à les chiffrer à nouveau à l’aide de la nouvelle clé principale de colonne et à mettre à jour les métadonnées pour les deux types de clés. La permutation des clés principales de colonne peut être effectuée avec ou sans séparation des rôles (lors de l’utilisation du module SqlServer PowerShell).
Permutation des clés principales de colonne sans séparation des rôles
La méthode décrite dans cette section pour la rotation d’une clé principale de colonne ne prend pas en charge la séparation des rôles entre un administrateur de sécurité et un administrateur de base de données. Certaines des étapes suivantes combinent des opérations sur les clés physiques avec des opérations sur les métadonnées de clé. Utilisez ce flux de travail si votre organisation utilise le modèle DevOps ou lorsque votre base de données est hébergée dans le cloud et que l’objectif principal est de restreindre les administrateurs cloud (mais pas les administrateurs de base de données locaux) à accéder aux données sensibles. N’utilisez pas cette méthode si les adversaires potentiels incluent des administrateurs de base de données ou si les administrateurs de base de données ne doivent pas avoir accès aux données sensibles.
| Tâche | Article | Accède aux clés en texte clair/au magasin de clés | Accède à la base de données |
|---|---|---|---|
| Étape 1. Créer une clé principale de colonne dans un magasin de clés. Note: Le module SqlServer PowerShell ne prend pas en charge cette étape. Pour accomplir cette tâche à partir de la ligne de commande, vous devez utiliser des outils propres à votre magasin de clés. Lorsque vous utilisez Azure Key Vault comme magasin de clés, la rotation des clés multilocataires gérées par le client n’est pas prise en charge. Vérifiez que la nouvelle clé gérée par le client est dans la même instance que celle existante. |
Créer et stocker des clés principales de colonne pour Always Encrypted | Oui | Non |
| Étape 2. Démarrer un environnement PowerShell et importer le module SQL Server. | Importer le module SqlServer | Non | Non |
| Étape 3. Se connecter à votre serveur et à la base de données. | Connexion à une base de données | Non | Oui |
| Étape 4. Créer un objet SqlColumnMasterKeySettings qui contient des informations sur l’emplacement de votre nouvelle clé principale de colonne. SqlColumnMasterKeySettings est un objet qui existe en mémoire (dans PowerShell). Pour le créer, utilisez l’applet de commande propre à votre magasin de clés. |
New-SqlAzureKeyVaultColumnMasterKeySettings New-SqlCertificateStoreColumnMasterKeySettings new-SqlCngColumnMasterKeySettings new-SqlCspColumnMasterKeySettings |
Non | Non |
| Étape 5. Créer les métadonnées relatives à votre nouvelle clé principale de colonne dans votre base de données. |
New-SqlColumnMasterKey Remarque : sous les couvertures, cette applet de commande émet l’instruction CREATE COLUMN MASTER KEY (Transact-SQL) pour créer des métadonnées clés. |
Non | Oui |
| Étape 6. S’authentifier auprès d’Azure, si votre clé principale de colonne actuelle ou nouvelle est stockée dans un coffre de clés ou dans un HSM managé dans Azure Key Vault | Connect-AzAccount | Oui | Non |
| Étape 7. Obtenez un jeton d’accès pour Azure Key Vaults, si votre clé principale de colonne est stockée dans Azure Key Vault. | Get-AzAccessToken | Non | Non |
| Étape 8 : Démarrer la permutation, en chiffrant chacune des clés de chiffrement de colonne, qui sont actuellement protégées avec l’ancienne clé principale de colonne, à l’aide de la nouvelle clé principale de colonne. Après cette étape, chaque clé de chiffrement de colonne concernée (associée à l'ancienne clé principale de colonne faisant l'objet d'une rotation) est chiffrée avec à la fois l'ancienne et la nouvelle clé principale de colonne, et possède deux valeurs chiffrées dans les métadonnées de la base de données. | Invoke-SqlColumnMasterKeyRotation (Rotation de la clé principale de colonne SQL) | Oui | Oui |
| Étape 9. Assurer la coordination avec les administrateurs de toutes les applications qui interrogent des colonnes chiffrées dans la base de données (et qui sont protégées avec l’ancienne clé principale de colonne), pour qu’ils puissent garantir que les applications peuvent accéder à la nouvelle clé principale de colonne. | Créer et stocker des clés principales de colonne (Always Encrypted) | Oui | Non |
| Étape 10. Compléter la rotation. Remarque : avant d’exécuter cette étape, vérifiez que toutes les applications qui interrogent des colonnes chiffrées protégées par l’ancienne clé principale de colonne ont été configurées pour utiliser la nouvelle clé principale de colonne. Si vous effectuez cette étape prématurément, certaines de ces applications risquent de ne pas pouvoir déchiffrer les données. Terminez la rotation en supprimant les valeurs chiffrées de la base de données qui ont été créées avec l’ancienne clé principale de colonne. Cette opération supprime l’association entre l’ancienne clé principale de colonne et les clés de chiffrement de colonne qu’elle protège. |
Complete-SqlColumnMasterKeyRotation | Non | Oui |
| Étape 11. Supprimer les métadonnées de l’ancienne clé principale de colonne. | remove-sqlColumnMasterKey | Non | Oui |
Remarque
Nous vous recommandons vivement de ne pas supprimer définitivement l’ancienne clé principale de colonne après la rotation. Au lieu de cela, laissez l’ancienne clé principale de colonne dans son magasin de clés actuel ou archivez-la dans un autre emplacement sécurisé. Si vous restaurez votre base de données à partir d’un fichier de sauvegarde à un point dans le temps avant la configuration de la nouvelle clé principale de colonne, vous aurez besoin de l’ancienne clé pour accéder aux données.
Rotation d'une clé principale de colonne sans séparation des rôles (exemple de certificat Windows)
Le script ci-dessous est un exemple de bout en bout qui remplace une clé principale de colonne existante (CMK1) par une nouvelle clé principale de colonne (CMK2).
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$ServerName = '<server name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$DatabaseName = '<database name>',
[Parameter(Mandatory = $false)]
[string]$CertificateSubject = 'AlwaysEncryptedCertNew',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$OldCmkName = 'CMK1',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$NewCmkName = 'CMK2'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop
Write-Host '[AE] Step 1: Creating a new self-signed certificate for the new CMK'
$cert = New-SelfSignedCertificate `
-Subject $CertificateSubject `
-CertStoreLocation 'Cert:CurrentUser\My' `
-KeyExportPolicy Exportable `
-Type DocumentEncryptionCert `
-KeyUsage KeyEncipherment `
-KeySpec KeyExchange `
-KeyLength 2048
Write-Host "[AE] Certificate created with thumbprint: $($cert.Thumbprint)"
Write-Host "[AE] Step 2: Connecting to SQL Server '$ServerName' / Database '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30"
try {
$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
}
catch {
Write-Error "Failed to connect to '$ServerName' / '$DatabaseName'. Verify instance, database, and local permissions."
throw
}
Write-Host "[AE] Step 3: Validating that old CMK '$OldCmkName' exists"
$oldCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $OldCmkName }
if (-not $oldCmk) {
throw "Old CMK '$OldCmkName' does not exist. Cannot rotate."
}
Write-Host "[AE] Old CMK '$OldCmkName' found."
Write-Host "[AE] Step 4: Creating CMK settings for new certificate"
$newCmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation 'CurrentUser' -Thumbprint $cert.Thumbprint
Write-Host "[AE] Step 5: Registering new CMK '$NewCmkName' in the database"
$newCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $NewCmkName }
if ($newCmk) {
Write-Host "[AE] New CMK '$NewCmkName' already exists. Skipping creation."
}
else {
New-SqlColumnMasterKey -Name $NewCmkName -InputObject $database -ColumnMasterKeySettings $newCmkSettings | Out-Null
Write-Host "[AE] New CMK '$NewCmkName' registered."
}
Write-Host "[AE] Step 6: Initiating CMK rotation from '$OldCmkName' to '$NewCmkName'"
Write-Host "[AE] (This re-encrypts all associated CEKs under the new CMK...)"
Invoke-SqlColumnMasterKeyRotation `
-SourceColumnMasterKeyName $OldCmkName `
-TargetColumnMasterKeyName $NewCmkName `
-InputObject $database
Write-Host "[AE] Rotation initiated."
Write-Host "[AE] Step 7: Completing the CMK rotation"
Complete-SqlColumnMasterKeyRotation `
-SourceColumnMasterKeyName $OldCmkName `
-InputObject $database
Write-Host "[AE] Rotation completed."
Write-Host "[AE] Step 8: Verifying CEKs are now under '$NewCmkName'"
$query = "SELECT name FROM sys.column_encryption_keys WHERE name = N'$($NewCmkName)'"
$rotatedCeks = Invoke-SqlCmd -ServerInstance $ServerName -Database $DatabaseName -Query $query -TrustServerCertificate -ErrorAction SilentlyContinue
if ($rotatedCeks) {
$cekCount = @($rotatedCeks).Count
if ($cekCount -eq 0) { $cekCount = 1 }
Write-Host "[AE] Verified: $cekCount CEK(s) now under '$NewCmkName'"
@($rotatedCeks) | ForEach-Object { Write-Host " - $($_.name)" }
}
Write-Host "[AE] Step 9: Removing old CMK metadata '$OldCmkName'"
Remove-SqlColumnMasterKey -Name $OldCmkName -InputObject $database
Write-Host "[AE] Old CMK '$OldCmkName' removed."
Write-Host '[AE] ========== Rotation Complete =========='
Write-Host "[AE] Old CMK: $OldCmkName (deleted)"
Write-Host "[AE] New CMK: $NewCmkName (active)"
Write-Host '[AE] All CEKs have been re-encrypted under the new CMK.'
Permutation des clés principales de colonne avec séparation des rôles
Le flux de travail de rotation de clé maîtresse de colonne décrit dans cette section garantit la séparation entre un administrateur de la sécurité et un DBA.
Important
Avant d’exécuter des étapes où Accesse les clés en texte clair/le magasin= de clésOui dans le tableau ci-dessous (étapes qui accèdent aux clés en texte clair ou au magasin de clés), assurez-vous que l’environnement PowerShell s’exécute sur un ordinateur sécurisé différent d’un ordinateur hébergeant votre base de données. Pour plus d’informations, consultez Considérations relatives à la sécurité pour la gestion des clés.
Partie 1 : Administrateur de base de données
Un administrateur de base de données récupère les métadonnées concernant la clé principale de colonne à faire tourner, et concernant les clés de chiffrement de colonne concernées, qui sont associées à la clé principale de colonne actuelle. L’administrateur de base de données partage toutes ces informations avec un administrateur de la sécurité.
| Tâche | Article | Accède aux clés en texte clair/au magasin de clés | Accède à la base de données |
|---|---|---|---|
| Étape 1. Démarrer un environnement PowerShell et importer le module SqlServer. | Importer le module SqlServer | Non | Aucun(e) |
| Étape 2. Se connecter à votre serveur et à une base de données. | Se connecter à une base de données | Non | Oui |
| Étape 3. Récupérer les métadonnées relatives à l’ancienne clé principale de colonne. | Get-SqlColumnMasterKey | Non | Oui |
| Étape 4. Récupérer les métadonnées relatives aux clés de chiffrement de colonne, protégées avec l’ancienne clé principale de colonne, notamment leurs valeurs chiffrées. | Get-SqlColumnEncryptionKey | Non | Oui |
| Étape 5. Partager l’emplacement de la clé principale de colonne (nom du fournisseur et chemin d’accès à la clé principale de colonne) et les valeurs chiffrées des clés de chiffrement de colonne correspondantes, protégées avec l’ancienne clé principale de colonne. | Considérons les exemples ci-dessous. | Non | Non |
Partie 2 : Administrateur de la sécurité
L'administrateur de sécurité génère une nouvelle clé principale de colonne, rechiffre les clés de chiffrement de colonne affectées avec cette nouvelle clé, et partage avec le DBA les informations sur la nouvelle clé principale de colonne ainsi que l'ensemble des nouvelles valeurs chiffrées pour les clés affectées.
| Tâche | Article | Accède aux clés en texte clair/au magasin de clés | Accède à la base de données |
|---|---|---|---|
| Étape 1. Obtenir auprès de votre administrateur de base de données l’emplacement de l’ancienne clé principale de colonne et les valeurs chiffrées des clés de chiffrement de colonne correspondantes, protégées avec l’ancienne clé principale de colonne. | N/A Considérons les exemples ci-dessous. |
Non | Non |
| Étape 2. Créer une clé principale de colonne dans un magasin de clés. Note: Le module SqlServer ne prend pas en charge cette étape. Pour accomplir cette tâche à partir d’une ligne de commande, vous devez utiliser les outils propres au type de votre magasin de clés. Lorsque vous utilisez Azure Key Vault comme magasin de clés, la rotation des clés multilocataires gérées par le client n’est pas prise en charge. Vérifiez que la nouvelle clé gérée par le client est dans la même instance que celle existante. |
Créer et stocker des clés principales de colonne pour Always Encrypted | Oui | Non |
| Étape 3. Démarrer un environnement PowerShell et importer le module SqlServer. | Importer le module SqlServer | Non | Non |
| Étape 4. Créez un objet SqlColumnMasterKeySettings qui contient des informations sur l’emplacement de votre ancienne clé principale de colonne. SqlColumnMasterKeySettings est un objet qui existe en mémoire (dans PowerShell). | New-SqlColumnMasterKeySettings | Non | Non |
| Étape 5. Créez un objet SqlColumnMasterKeySettings qui contient des informations sur l’emplacement de votre nouvelle clé principale de colonne. SqlColumnMasterKeySettings est un objet qui existe en mémoire (dans PowerShell). Pour le créer, utilisez l’applet de commande propre à votre magasin de clés. |
New-SqlAzureKeyVaultColumnMasterKeySettings New-SqlCertificateStoreColumnMasterKeySettings new-SqlCngColumnMasterKeySettings new-SqlCspColumnMasterKeySettings |
Non | Non |
| Étape 6. S’authentifier auprès d’Azure, si votre clé ancienne principale de colonne (actuelle) ou votre nouvelle clé principale de colonne est stockée dans un coffre de clés ou dans un HSM managé dans Azure Key Vault. | Connect-AzAccount | Oui | Non |
| Étape 7. Obtenez un jeton d’accès pour Azure Key Vaults, si votre clé principale de colonne est stockée dans Azure Key Vault. | Get-AzAccessToken | Non | Non |
| Étape 8 : Rechiffrer chaque valeur de la clé de chiffrement de colonne, qui est actuellement protégée avec l’ancienne clé principale de colonne, à l’aide de la nouvelle clé principale de colonne. |
New-SqlColumnEncryptionKeyEncryptedValue Note : Lors de l’appel de cette applet de commande, passez les objets SqlColumnMasterKeySettings pour l'ancienne et la nouvelle clé principale de colonne, ainsi qu’une valeur de la clé de chiffrement de colonne, à rechiffrer. |
Oui | Non |
| Étape 9. Partager avec votre administrateur de base de données l’emplacement de la nouvelle clé principale de colonne (nom du fournisseur et chemin d’accès à la clé principale de colonne) et l’ensemble des nouvelles valeurs chiffrées des clés de chiffrement de colonne. | Considérons les exemples ci-dessous. | Non | Non |
Remarque
Nous vous recommandons vivement de ne pas supprimer définitivement l’ancienne clé principale de colonne après la rotation. Au lieu de cela, laissez l’ancienne clé principale de colonne dans son magasin de clés actuel ou archivez-la dans un autre emplacement sécurisé. Si vous restaurez votre base de données à partir d’un fichier de sauvegarde à un point dans le temps avant la configuration de la nouvelle clé principale de colonne, vous aurez besoin de l’ancienne clé pour accéder aux données.
Partie 3 : Administrateur de base de données
L’administrateur de base de données crée des métadonnées pour la nouvelle clé principale de colonne et met à jour les métadonnées des clés de chiffrement de colonne concernées, pour ajouter le nouvel ensemble de valeurs chiffrées. Lors de cette étape, l’administrateur de base de données travaille également en coordination avec les administrateurs des applications qui interrogent des colonnes de chiffrement, qui garantissent que l’application peut accéder à la nouvelle clé principale de colonne. Une fois que toutes les applications sont configurées pour utiliser la nouvelle clé principale de colonne, l’administrateur de base de données supprime l’ancien ensemble de valeurs chiffrées et les anciennes métadonnées de clés principales de colonne.
| Tâche | Article | Accède aux clés en texte clair/au magasin de clés | Accède à la base de données |
|---|---|---|---|
| Étape 1. Obtenir auprès de votre administrateur de la sécurité l’emplacement de la nouvelle clé principale de colonne et le nouvel ensemble de valeurs chiffrées des clés de chiffrement de colonne correspondantes, protégées avec l’ancienne clé principale de colonne. | Considérons les exemples ci-dessous. | Non | Non |
| Étape 2. Démarrer un environnement PowerShell et importer le module SqlServer. | Importer le module SqlServer | Non | Non |
| Étape 3. Se connecter à votre serveur et à une base de données. | Connexion à une base de données | Non | Oui |
| Étape 4. Créer un objet SqlColumnMasterKeySettings qui contient des informations sur l’emplacement de votre nouvelle clé principale de colonne. SqlColumnMasterKeySettings est un objet qui existe en mémoire (dans PowerShell). | New-SqlColumnMasterKeySettings | Non | Non |
| Étape 5. Créer les métadonnées relatives à votre nouvelle clé principale de colonne dans votre base de données. |
New-SqlColumnMasterKey Remarque : En arrière-plan, cette applet de commande exécute l’instruction CREATE COLUMN MASTER KEY (Transact-SQL) pour créer les métadonnées de la clé. |
Non | Oui |
| Étape 6. Récupérer les métadonnées relatives aux clés de chiffrement de colonne, protégées avec l’ancienne clé principale de colonne. | Get-SqlColumnEncryptionKey | Non | Oui |
| Étape 7. Ajouter une nouvelle valeur chiffrée (générée à l’aide de la nouvelle clé principale de colonne) aux métadonnées pour chaque clé de chiffrement de colonne concernée. | Add-SqlColumnEncryptionKeyValue | Non | Oui |
| Étape 8 : Assurer la coordination avec les administrateurs de toutes les applications qui interrogent des colonnes chiffrées dans la base de données (et qui sont protégées avec l’ancienne clé principale de colonne), pour qu’ils puissent garantir que les applications peuvent accéder à la nouvelle clé principale de colonne. | Création et stockage des clés principales de colonne (Always Encrypted) | Non | Non |
| Étape 9. Terminer la permutation en supprimant de la base de données les valeurs chiffrées associées à l’ancienne clé principale de colonne. Note: Avant d’exécuter cette étape, vérifiez que toutes les applications qui interrogent les colonnes chiffrées protégées par l’ancienne clé principale de colonne ont été configurées pour utiliser la nouvelle clé principale de colonne. Si vous effectuez cette étape prématurément, certaines de ces applications risquent de ne pas pouvoir déchiffrer les données. Cette étape supprime une association entre l’ancienne clé principale de colonne et les clés de chiffrement de colonne qu’elle protège. |
Complete-SqlColumnMasterKeyRotation Vous pouvez également utiliser Remove-SqlColumnEncryptionKeyValue |
Non | Oui |
| Étape 10. Supprimer de la base de données les métadonnées de l’ancienne clé principale de colonne. | remove-sqlColumnMasterKey | Non | Oui |
Rotation d’une clé maître de colonne avec séparation des rôles (exemple de certificat Windows)
Le script ci-dessous est un exemple de bout en bout montrant comment générer une nouvelle clé principale de colonne, qui est un certificat dans le Windows Certificate Store, et remplacer la clé principale de colonne existante (actuelle) par cette nouvelle clé principale de colonne. Le script part du principe que la base de données cible contient la clé principale de colonne, nommée CMK1 (à permuter), qui chiffre certaines clés de chiffrement de colonne.
Partie 1 : Administrateur de base de données
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$ServerName = '<server name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$DatabaseName = '<database name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$OldCmkName = 'CMK2',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$OutputFolder = 'C:\temp'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop
Write-Host "[CEK Export] Starting CMK and CEK data export"
# Validate output folder
if (-not (Test-Path -Path $OutputFolder -PathType Container)) {
Write-Host "[CEK Export] Creating output folder: $OutputFolder"
New-Item -Path $OutputFolder -ItemType Directory | Out-Null
}
# Connect to database
Write-Host "[CEK Export] Connecting to '$ServerName' / '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;TrustServerCertificate=True;Connection Timeout=30"
try {
$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
}
catch {
Write-Error "Failed to connect to '$ServerName' / '$DatabaseName'."
throw
}
# Retrieve old CMK
Write-Host "[CEK Export] Retrieving CMK '$OldCmkName'"
$oldCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $OldCmkName }
if (-not $oldCmk) {
throw "CMK '$OldCmkName' not found in database '$DatabaseName'."
}
# Export CMK metadata using fixed text file name
$cmkFile = Join-Path $OutputFolder "oldcmkdata.txt"
Write-Host "[CEK Export] Exporting CMK metadata to: $cmkFile"
"CMKName|KeyStoreProviderName|KeyPath" | Set-Content -Path $cmkFile -Encoding UTF8
"$OldCmkName|$($oldCmk.KeyStoreProviderName)|$($oldCmk.KeyPath)" | Add-Content -Path $cmkFile -Encoding UTF8
Write-Host "[CEK Export] ✓ CMK metadata exported"
# Discover and export CEKs using fixed text file name
Write-Host "[CEK Export] Discovering CEKs associated with '$OldCmkName'"
$ceks = Get-SqlColumnEncryptionKey -InputObject $database
$cekFile = Join-Path $OutputFolder "oldcekvalues.txt"
"CEKName|CEKEncryptedValue|HasMultipleEncryptedValues" | Set-Content -Path $cekFile -Encoding UTF8
$exportedCount = 0
$multiValueCount = 0
foreach ($cek in $ceks) {
if (-not $cek.ColumnEncryptionKeyValues) {
continue
}
# Check if this CEK has multiple encrypted values
if ($cek.ColumnEncryptionKeyValues.Count -gt 1) {
# CEK has multiple encrypted values - check if any reference the old CMK
$refersToOldCmk = $cek.ColumnEncryptionKeyValues | Where-Object { $_.ColumnMasterKeyName -eq $OldCmkName }
if ($refersToOldCmk) {
Write-Warning "CEK '$($cek.Name)' has $($cek.ColumnEncryptionKeyValues.Count) encrypted values. One references '$OldCmkName'. This CEK cannot be rotated automatically."
"$($cek.Name)|MULTIPLE_ENCRYPTED_VALUES|True" | Add-Content -Path $cekFile -Encoding UTF8
$multiValueCount++
}
}
else {
# CEK has single encrypted value - check if it references the old CMK
if ($cek.ColumnEncryptionKeyValues[0].ColumnMasterKeyName -eq $OldCmkName) {
$encryptedValueHex = "0x" + -join ($cek.ColumnEncryptionKeyValues[0].EncryptedValue | ForEach-Object { $_.ToString("X2") })
"$($cek.Name)|$encryptedValueHex|False" | Add-Content -Path $cekFile -Encoding UTF8
$exportedCount++
}
}
}
Write-Host "[CEK Export] ✓ CEK encrypted values exported"
Write-Host "[CEK Export] - Exported: $exportedCount CEK(s)"
if ($multiValueCount -gt 0) {
Write-Warning " - Multi-valued CEKs (manual review needed): $multiValueCount"
}
Write-Host "[CEK Export] ===== Export Complete ====="
Write-Host "[CEK Export] CMK Metadata: $cmkFile"
Write-Host "[CEK Export] CEK Values: $cekFile"
Partie 2 : Administrateur de la sécurité
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$ShareFolder = 'C:\Temp\',
[Parameter(Mandatory = $false)]
[ValidateSet('CurrentUser', 'LocalMachine')]
[string]$StoreLocation = 'CurrentUser',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$CertificateSubject = 'AlwaysEncryptedCert'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop
function Import-DelimitedTextFile {
param(
[Parameter(Mandatory = $true)] [string]$Path,
[Parameter(Mandatory = $true)] [string[]]$RequiredColumns
)
if (-not (Test-Path -Path $Path -PathType Leaf)) {
throw "Required file not found: $Path"
}
$raw = Get-Content -Path $Path -Raw
if ([string]::IsNullOrWhiteSpace($raw)) {
throw "File is empty: $Path"
}
$delimiter = if ($raw -match '\|') { '|' } else { ',' }
$rows = @(Import-Csv -Path $Path -Delimiter $delimiter)
if ($rows.Count -eq 0) {
throw "No data rows found in file: $Path"
}
$first = $rows[0]
$RequiredColumns | ForEach-Object {
if (-not $first.PSObject.Properties[$_]) {
throw "Missing required column '$_' in file: $Path"
}
}
return $rows
}
if (-not (Test-Path -Path $ShareFolder -PathType Container)) {
throw "Share folder does not exist: $ShareFolder"
}
$oldCmkDataFile = Join-Path $ShareFolder 'oldcmkdata.txt'
$oldCekValuesFile = Join-Path $ShareFolder 'oldcekvalues.txt'
$newCmkDataFile = Join-Path $ShareFolder 'newcmkdata.txt'
$newCekValuesFile = Join-Path $ShareFolder 'newcekvalues.txt'
Write-Host "[AE] Reading old CMK data from '$oldCmkDataFile'"
$oldCmkDataRows = Import-DelimitedTextFile -Path $oldCmkDataFile -RequiredColumns @('KeyStoreProviderName', 'KeyPath')
$oldCmkData = $oldCmkDataRows[0]
Write-Host "[AE] Reading old CEK values from '$oldCekValuesFile'"
$oldCekValues = Import-DelimitedTextFile -Path $oldCekValuesFile -RequiredColumns @('CEKName', 'CEKEncryptedValue')
Write-Host "[AE] Finding or creating certificate '$CertificateSubject' in $StoreLocation\\My"
$certPath = "Cert:$StoreLocation\My"
$cert = Get-ChildItem -Path $certPath |
Where-Object { $_.Subject -eq "CN=$CertificateSubject" } |
Sort-Object NotAfter -Descending |
Select-Object -First 1
if (-not $cert) {
$cert = New-SelfSignedCertificate `
-Subject $CertificateSubject `
-CertStoreLocation $certPath `
-KeyExportPolicy Exportable `
-Type DocumentEncryptionCert `
-KeyUsage DataEncipherment `
-KeySpec KeyExchange
}
Write-Host '[AE] Building CMK settings'
$oldCmkSettings = New-SqlColumnMasterKeySettings `
-KeyStoreProviderName $oldCmkData.KeyStoreProviderName `
-KeyPath $oldCmkData.KeyPath
$newCmkSettings = New-SqlCertificateStoreColumnMasterKeySettings `
-CertificateStoreLocation $StoreLocation `
-Thumbprint $cert.Thumbprint
Write-Host "[AE] Re-encrypting CEK values and writing '$newCekValuesFile'"
"CEKName|CEKEncryptedValue" | Set-Content -Path $newCekValuesFile -Encoding UTF8
$oldCekValues | ForEach-Object {
$newValue = New-SqlColumnEncryptionKeyEncryptedValue `
-TargetColumnMasterKeySettings $newCmkSettings `
-ColumnMasterKeySettings $oldCmkSettings `
-EncryptedValue $_.CEKEncryptedValue
"$($_.CEKName)|$newValue" | Add-Content -Path $newCekValuesFile -Encoding UTF8
}
Write-Host "[AE] Writing new CMK data to '$newCmkDataFile'"
"KeyStoreProviderName|KeyPath" | Set-Content -Path $newCmkDataFile -Encoding UTF8
"$($newCmkSettings.KeyStoreProviderName)|$($newCmkSettings.KeyPath)" | Add-Content -Path $newCmkDataFile -Encoding UTF8
Write-Host '[AE] Completed successfully'
Write-Host "[AE] Output files: $newCmkDataFile , $newCekValuesFile"
Partie 3 : Administrateur de base de données
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$ServerName = '<server name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$DatabaseName = '<database name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$OldCmkName = 'CMK1',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$NewCmkName = 'CMK2',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$InputFolder = 'C:\temp'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop
function Import-DelimitedTextFile {
param(
[Parameter(Mandatory = $true)] [string]$Path,
[Parameter(Mandatory = $true)] [string[]]$RequiredColumns
)
if (-not (Test-Path -Path $Path -PathType Leaf)) {
throw "Required file not found: $Path"
}
$raw = Get-Content -Path $Path -Raw
if ([string]::IsNullOrWhiteSpace($raw)) {
throw "File is empty: $Path"
}
$delimiter = if ($raw -match '\|') { '|' } else { ',' }
$rows = @(Import-Csv -Path $Path -Delimiter $delimiter)
if ($rows.Count -eq 0) {
throw "No data rows found in file: $Path"
}
$first = $rows[0]
$RequiredColumns | ForEach-Object {
if (-not $first.PSObject.Properties[$_]) {
throw "Missing required column '$_' in file: $Path"
}
}
return $rows
}
if (-not (Test-Path -Path $InputFolder -PathType Container)) {
throw "Input folder not found: $InputFolder"
}
$newCmkDataFile = Join-Path $InputFolder 'newcmkdata.txt'
$newCekValuesFile = Join-Path $InputFolder 'newcekvalues.txt'
Write-Host "[AE] Reading new CMK data from '$newCmkDataFile'"
$newCmkRows = Import-DelimitedTextFile -Path $newCmkDataFile -RequiredColumns @('KeyStoreProviderName', 'KeyPath')
$newCmkData = $newCmkRows[0]
Write-Host "[AE] Reading new CEK values from '$newCekValuesFile'"
$newCekValues = Import-DelimitedTextFile -Path $newCekValuesFile -RequiredColumns @('CEKName', 'CEKEncryptedValue')
Write-Host "[AE] Connecting to '$ServerName' / '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;TrustServerCertificate=True;Connection Timeout=30"
$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
Write-Host "[AE] Ensuring target CMK '$NewCmkName' exists"
$newCmkSettings = New-SqlColumnMasterKeySettings -KeyStoreProviderName $newCmkData.KeyStoreProviderName -KeyPath $newCmkData.KeyPath
$existingNewCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $NewCmkName }
if (-not $existingNewCmk) {
New-SqlColumnMasterKey -Name $NewCmkName -InputObject $database -ColumnMasterKeySettings $newCmkSettings | Out-Null
}
Write-Host "[AE] Adding new encrypted CEK values under '$NewCmkName'"
$ceks = Get-SqlColumnEncryptionKey -InputObject $database
$ceksToRotate = @(
$ceks | Where-Object {
$_.ColumnEncryptionKeyValues -and
@($_.ColumnEncryptionKeyValues | Where-Object { $_.ColumnMasterKeyName -eq $OldCmkName }).Count -gt 0
}
)
$ceksToRotate | ForEach-Object {
$cek = $_
if (@($cek.ColumnEncryptionKeyValues).Count -gt 1) {
throw "CEK '$($cek.Name)' already has multiple encrypted values and still references '$OldCmkName'."
}
$newValueRow = @($newCekValues | Where-Object { $_.CEKName -eq $cek.Name }) | Select-Object -First 1
if (-not $newValueRow) {
throw "No new encrypted value found for CEK '$($cek.Name)' in file '$newCekValuesFile'."
}
Add-SqlColumnEncryptionKeyValue `
-ColumnMasterKeyName $NewCmkName `
-Name $cek.Name `
-EncryptedValue $newValueRow.CEKEncryptedValue `
-InputObject $database | Out-Null
}
Write-Host "[AE] Completing rotation for source CMK '$OldCmkName'"
Complete-SqlColumnMasterKeyRotation -SourceColumnMasterKeyName $OldCmkName -InputObject $database
Write-Host "[AE] Removing source CMK '$OldCmkName' metadata"
Remove-SqlColumnMasterKey -Name $OldCmkName -InputObject $database
Write-Host '[AE] Completed successfully'
Rotation d’une clé de chiffrement de colonne
La permutation d’une clé de chiffrement de colonne implique le déchiffrement des données dans toutes les colonnes, chiffrées avec la clé à permuter, et le rechiffrement des données à l’aide de la nouvelle clé de chiffrement de colonne. Ce flux de travail de permutation nécessite l’accès aux clés et à la base de données. Il ne peut donc pas être effectué avec la séparation des rôles. La permutation d’une clé de chiffrement de colonne peut prendre beaucoup de temps si les tables qui contiennent les colonnes chiffrées avec la clé soumise à la permutation sont volumineuses. Votre organisation doit donc apporter un soin particulier à la planification de la permutation des clés de chiffrement de colonne.
Vous pouvez faire pivoter une clé de chiffrement de colonne à l’aide d’une approche hors ligne ou en ligne. La première méthode est susceptible d’être plus rapide, mais vos applications ne peuvent pas écrire dans les tables concernées. La deuxième approche est susceptible de prendre plus de temps. Cependant, vous pouvez limiter l’intervalle de temps pendant lequel les tables concernées sont indisponibles pour les applications. Pour plus d’informations, consultez Configurer le chiffrement de colonne à l’aide d’Always Encrypted avec PowerShell et Set-SqlColumnEncryption.
| Tâche | Article | Accède aux clés en texte clair/au magasin de clés | Accède à la base de données |
|---|---|---|---|
| Étape 1. Démarrer un environnement PowerShell et importer le module SqlServer. | Importer le module SqlServer | Non | Non |
| Étape 2. Se connecter à votre serveur et à une base de données. | Connexion à une base de données | Non | Oui |
| Étape 3. S’authentifier auprès d’Azure, si votre clé principale de colonne (qui protège la clé de chiffrement de colonne, soumise à permutation) est stockée dans un coffre de clés ou dans un HSM managé dans Azure Key Vault. | Connect-AzAccount | Oui | Non |
| Étape 4. Obtenez un jeton d’accès pour Azure Key Vaults, si votre clé principale de colonne est stockée dans Azure Key Vault. | Get-AzAccessToken | Non | Non |
| Étape 5. Générer une clé de chiffrement de colonne, la chiffrer avec la clé principale de colonne et créer les métadonnées de clé de chiffrement de colonne dans la base de données. |
New-SqlColumnEncryptionKey Note: Utilisez une variante de l’applet de commande qui génère et chiffre en interne une clé de chiffrement de colonne. En arrière-plan, ce cmdlet exécute l’instruction CREATE COLUMN ENCRYPTION KEY (Transact-SQL) afin de créer les métadonnées de la clé. |
Oui | Oui |
| Étape 6. Rechercher toutes les colonnes chiffrées avec l’ancienne clé de chiffrement de colonne. | Guide de programmation SMO (SQL Server Management Objects) | Non | Oui |
| Étape 7. Créez un objet SqlColumnEncryptionSettings pour chaque colonne affectée. SqlColumnEncryptionSettings est un objet qui existe en mémoire (dans PowerShell). Il spécifie le schéma de chiffrement cible pour une colonne. Dans ce cas, l’objet doit spécifier que la colonne concernée doit être chiffrée à l’aide de la nouvelle clé de chiffrement de colonne. | New-SqlColumnEncryptionSettings | Non | Non |
| Étape 8 : Rechiffrer les colonnes identifiées à l’étape 5 à l’aide de la nouvelle clé de chiffrement de colonne. |
Set-SqlColumnEncryption Note: Cette étape peut prendre beaucoup de temps. Vos applications ne peuvent pas accéder aux tables pendant toute la durée de l’opération ou pendant une partie de l’opération, en fonction de l’approche sélectionnée (en ligne ou hors ligne). |
Oui | Oui |
| Étape 9. Supprimer les métadonnées de l’ancienne clé de chiffrement de colonne. | remove-sqlColumnEncryptionKey | Non | Oui |
Exemple : permutation d’une clé de chiffrement de colonne
Le script ci-dessous illustre la permutation d’une clé de chiffrement de colonne. Ce script part du principe que la base de données cible contient certaines colonnes chiffrés avec une clé de chiffrement de colonne, nommée CEK1 (à permuter), qui est protégée à l’aide d’une clé principale de colonne, nommée CMK1 (la clé principale de colonne n’est pas stockée dans Azure Key Vault).
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$ServerName = '<server name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$DatabaseName = '<database name>',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$OldCekName = 'CEK1',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$NewCekName = 'CEK2',
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$CmkName = 'CMK2',
[Parameter(Mandatory = $false)]
[ValidateRange(0, 3600)]
[int]$MaxDowntimeInSeconds = 120,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$LogFileDirectory = '.'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop
if ($OldCekName -eq $NewCekName) {
throw 'OldCekName and NewCekName must be different.'
}
if (-not (Test-Path -Path $LogFileDirectory -PathType Container)) {
New-Item -Path $LogFileDirectory -ItemType Directory | Out-Null
}
Write-Host "[AE] Connecting to '$ServerName' / '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;TrustServerCertificate=True;Connection Timeout=30"
$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$cmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $cmk) {
throw "Column master key '$CmkName' was not found."
}
Write-Host "[AE] Ensuring target CEK '$NewCekName' exists"
$existingNewCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $NewCekName }
if (-not $existingNewCek) {
New-SqlColumnEncryptionKey -Name $NewCekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}
Write-Host "[AE] Discovering encrypted columns using '$OldCekName'"
$settings = @()
$tables = @($database.Tables)
$tables | ForEach-Object {
$table = $_
@($table.Columns) | ForEach-Object {
$column = $_
if ($column.IsEncrypted -and $column.ColumnEncryptionKeyName -eq $OldCekName) {
$columnName = "{0}.{1}.{2}" -f $table.Schema, $table.Name, $column.Name
$settings += New-SqlColumnEncryptionSettings -ColumnName $columnName -EncryptionType $column.EncryptionType -EncryptionKey $NewCekName
}
}
}
if ($settings.Count -eq 0) {
Write-Warning "No encrypted columns found that reference '$OldCekName'. Nothing to rotate."
return
}
Write-Host "[AE] Re-encrypting $($settings.Count) column(s) to '$NewCekName'"
Set-SqlColumnEncryption `
-ColumnEncryptionSettings $settings `
-InputObject $database `
-UseOnlineApproach `
-MaxDowntimeInSeconds $MaxDowntimeInSeconds `
-LogFileDirectory $LogFileDirectory
Write-Host "[AE] Validating no columns still reference '$OldCekName'"
$stillUsingOld = $false
@($database.Tables) | ForEach-Object {
@($_.Columns) | ForEach-Object {
if ($_.IsEncrypted -and $_.ColumnEncryptionKeyName -eq $OldCekName) {
$stillUsingOld = $true
}
}
}
if ($stillUsingOld) {
throw "At least one encrypted column still references '$OldCekName'. Aborting CEK removal."
}
Write-Host "[AE] Removing old CEK '$OldCekName'"
Remove-SqlColumnEncryptionKey -Name $OldCekName -InputObject $database
Write-Host '[AE] Completed successfully'
Étapes suivantes
- Interroger des colonnes à l’aide d’Always Encrypted avec SQL Server Management Studio
- Développer des applications à l’aide d’Always Encrypted
Voir aussi
- Always Encrypted
- Vue d’ensemble de la gestion des clés pour Always Encrypted
- Configurer Always Encrypted à l’aide de PowerShell
- Faire pivoter des clés Always Encrypted à l’aide de SQL Server Management Studio
- CREATE COLUMN MASTER KEY (Transact-SQL)
- DROP COLUMN MASTER KEY (Transact-SQL)
- CREATE COLUMN ENCRYPTION KEY (Transact-SQL)
- ALTER COLUMN ENCRYPTION KEY (Transact-SQL)
- DROP COLUMN ENCRYPTION KEY (Transact-SQL)
- sys.column_master_keys (Transact-SQL)
- sys.column_encryption_keys (Transact-SQL)