Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
platí pro:SQL Server
Azure SQL Database
azure SQL Managed Instance
Tento článek obsahuje postup obměna klíčů pro Always Encrypted pomocí modulu SqlServer PowerShell. Informace o tom, jak začít používat modul SqlServer PowerShell pro Always Encrypted, najdete v tématu Konfigurace funkce Always Encrypted pomocí powershellového.
Poznámka
Microsoft doporučuje používat PowerShell 7 nebo novější při spouštění skriptů PowerShellu Always Encrypted. PowerShell 7 poskytuje vylepšenou podporu pro různé platformy, lepší výkon a nejnovější kompatibilitu s modulem SqlServer (v22+), který je nutný pro mnoho scénářů Always Encrypted.
Obměna klíčů Always Encrypted je proces nahrazení existujícího klíče novým klíčem. Klíč možná budete muset otočit, pokud je napadený, nebo dodržet zásady nebo předpisy vaší organizace, které vyžadují pravidelnou obměnu kryptografických klíčů.
Funkce Always Encrypted používá dva typy klíčů, takže existují dva vysoké úrovně pracovních postupů obměny klíčů: obměna hlavních klíčů sloupců a obměna šifrovacích klíčů sloupců.
- Obnovení šifrovacího klíče sloupce – zahrnuje dešifrování dat šifrovaných aktuálním klíčem a opětovné zašifrování dat pomocí nového šifrovacího klíče sloupce. Vzhledem k tomu, že rotace šifrovacího klíče sloupce vyžaduje přístup ke klíčům i k databázi, lze obměnu šifrovacích klíčů sloupců provést pouze bez oddělení rolí.
- Rotace hlavního klíče sloupce – zahrnuje dešifrování šifrovacích klíčů sloupců, které jsou chráněny aktuálním hlavním klíčem sloupce, jejich opětovné šifrování pomocí nového hlavního klíče sloupce a aktualizaci metadat pro oba typy klíčů. Rotace hlavního klíče sloupce může být dokončena s oddělením rolí nebo bez něj (při použití modulu SqlServer PowerShell).
Obměna hlavního klíče sloupce bez oddělení rolí
Metoda popsaná v této části pro otáčení hlavního klíče sloupce nepodporuje oddělení rolí mezi správcem zabezpečení a DBA. Některé z následujících kroků kombinují operace s fyzickými klíči a operace s metadaty klíčů. Tento pracovní postup použijte, pokud vaše organizace používá model DevOps nebo když je vaše databáze hostovaná v cloudu a primárním cílem je omezit správce cloudu (ale ne místní dbA) v přístupu k citlivým datům. Tuto metodu nepoužívejte, pokud mezi potenciální útočníky patří správci databáze nebo pokud by správci databáze neměli mít přístup k citlivým datům.
| Úkol | Článek | Přístup ke klíčům nebo úložišti klíčů ve formátu prostého textu | Přístup k databázi |
|---|---|---|---|
| Krok 1. Vytvořte nový klíč hlavního sloupce v úložišti klíčů. Poznámka: modul SqlServer PowerShell tento krok nepodporuje. Pokud chcete tuto úlohu provést z příkazového řádku, musíte použít nástroje, které jsou specifické pro vaše úložiště klíčů. Při použití služby Azure Key Vault jako úložiště klíčů se nepodporuje rotace klíčů spravovaných zákazníkem v prostředí s více tenanty. Ujistěte se, že nový zákazníkem spravovaný klíč je ve stejném tenantovi jako existující klíč. |
Vytvoření a uložení hlavních klíčů sloupců pro funkci Always Encrypted | Ano | Ne |
| Krok 2. Spuštění prostředí PowerShellu a import modulu SqlServer | Import modulu SqlServer | Ne | Ne |
| Krok 3. Připojte se k serveru a databázi. | Připojení k databázi | Ne | Ano |
| Krok 4. Vytvořte objekt SqlColumnMasterKeySettings, který obsahuje informace o umístění nového hlavního klíče sloupce. SqlColumnMasterKeySettings je objekt, který existuje v paměti (v PowerShellu). Pokud ho chcete vytvořit, musíte použít rutinu, která je specifická pro vaše úložiště klíčů. |
New-SqlAzureKeyVaultColumnMasterKeySettings Nové nastavení hlavního klíče sloupce úložiště certifikátů SQL New-SqlCngColumnMasterKeySettings Nové nastavení hlavního klíče sloupce SQL CNG Nové nastavení klíče ColumnMasterKey pro SqlCsp |
Ne | Ne |
| Krok 5. Vytvořte metadata o novém hlavním klíči sloupce v databázi. |
New-SqlColumnMasterKey Poznámka: V rámci popisů tato rutina CREATE COLUMN MASTER KEY vydává příkaz (Transact-SQL) pro vytvoření klíčových metadat. |
Ne | Ano |
| Krok 6. Ověřte se v Azure, pokud je váš aktuální hlavní klíč sloupce nebo nový hlavní klíč sloupce uložený v trezoru klíčů nebo spravovaném HSM ve službě Azure Key Vault. | Connect-AzAccount | Ano | Ne |
| Krok 7. Získejte přístupový token pro službu Azure Key Vault, pokud je hlavní klíč sloupce uložený ve službě Azure Key Vault. | Get-AzAccessToken | Ne | Ne |
| Krok 8. Zahajte otáčení tím, že pomocí nového hlavního klíče sloupce zašifrujte každý šifrovací klíč sloupce, který je aktuálně chráněný starým hlavním klíčem sloupce. Po tomto kroku je každý ovlivněný šifrovací klíč sloupce (spojený se starým hlavním klíčem sloupce, který se rotuje) zašifrován jak starým, tak novým hlavním klíčem sloupce a má dvě šifrované hodnoty v metadatech databáze. | Invoke-SqlColumnMasterKeyRotation | Ano | Ano |
| Krok 9. Koordinujte se správci všech aplikací, které dotazují šifrované sloupce v databázi (a jsou chráněné starým hlavním klíčem sloupce), aby mohly zajistit, aby aplikace mohly přistupovat k novému hlavnímu klíči sloupce. | Vytvoření a uložení mistrovských klíčů sloupců (Always Encrypted) | Ano | Ne |
| Krok 10. Dokončení otočení Poznámka: před provedením tohoto kroku se ujistěte, že všechny aplikace, které se dotazují na šifrované sloupce chráněné starým hlavním klíčem sloupce, byly nakonfigurovány tak, aby používaly nový hlavní klíč sloupce. Pokud tento krok provedete předčasně, některé z těchto aplikací nemusí být schopny data dešifrovat. Dokončete rotaci odebráním šifrovaných hodnot z databáze, které byly vytvořeny se starým hlavním klíčem sloupce. Tím se odstraní propojení mezi starým hlavním klíčem sloupce a šifrovacími klíči sloupců, které chrání. |
Dokončit-SqlColumnMasterKeyRotation | Ne | Ano |
| Krok 11. Odeberte metadata ze starého sloupcového hlavního klíče. | Remove-SqlColumnMasterKey | Ne | Ano |
Poznámka
Důrazně doporučujeme po rotaci trvale neodstraňovat starý hlavní klíč sloupce. Místo toho byste měli zachovat původní hlavní klíč sloupce v aktuálním úložišti klíčů nebo ho archivovat na jiném zabezpečeném místě. Pokud databázi obnovíte ze záložního souboru k určitému bodu v čase před konfigurace nového hlavního klíče sloupce, budete pro přístup k datům potřebovat starý klíč.
Rotace hlavního klíče sloupce bez oddělení rolí (příklad certifikátu systému Windows)
Následující skript je kompletní příklad, který nahradí stávající hlavní klíč sloupce (CMK1) novým hlavním klíčem sloupce (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.'
Rotace hlavního klíče sloupce s oddělením rolí
Pracovní postup rotace hlavního klíče sloupce popsaný v této části zajišťuje oddělení mezi správcem zabezpečení a administrátorem databáze (DBA).
Důležitý
Před provedením jakéhokoli postupu, kdy Accesses plaintext keys/keystore=Ano v následující tabulce (kroky, které přistupují ke klíčům prostého textu nebo úložišti klíčů), ujistěte se, že prostředí PowerShell běží na zabezpečeném počítači, který se liší od počítače hostujícího vaši databázi. Další informace najdete v tématu aspekty zabezpečení pro správu klíčů.
Část 1: DBA
Správce databáze načte metadata o hlavním klíči sloupce, který se má otočit, a o ovlivněných šifrovacích klíčích sloupců, které jsou přidružené k hlavnímu klíči aktuálního sloupce. Databázový administrátor sdílí všechny tyto informace se správcem zabezpečení.
| Úkol | Článek | Přístup ke klíčům nebo úložišti klíčů ve formátu prostého textu | Přístup k databázi |
|---|---|---|---|
| Krok 1. Spusťte prostředí PowerShell a naimportujte modul SqlServer. | Import modulu SqlServer | Ne | Žádný |
| Krok 2. Připojte se k serveru a databázi. | Připojit k databázi | Ne | Ano |
| Krok 3. Načtěte metadata o starém hlavním klíči pro sloupce. | Get-SqlColumnMasterKey | Ne | Ano |
| Krok 4. Načtěte metadata o šifrovacích klíčích sloupců chráněných starým hlavním klíčem sloupce včetně jejich šifrovaných hodnot. | Get-SqlColumnEncryptionKey | Ne | Ano |
| Krok 5. Sdílejte umístění hlavního klíče sloupce (název zprostředkovatele a cestu ke klíči hlavního klíče pro sloupec) a šifrované hodnoty odpovídajících šifrovacích klíčů sloupců, chráněné starým hlavním klíčem sloupce. | Podívejte se na následující příklady. | Ne | Ne |
Část 2: Správce zabezpečení
Správce zabezpečení vygeneruje nový hlavní klíč sloupce, znovu zašifruje ovlivněné šifrovací klíče sloupců pomocí nového hlavního klíče sloupce a sdílí informace o novém hlavním klíči sloupce a také sadu nových šifrovaných hodnot pro ovlivněné šifrovací klíče sloupců s DBA.
| Úkol | Článek | Přístup ke klíčům nebo úložišti klíčů ve formátu prostého textu | Přístup k databázi |
|---|---|---|---|
| Krok 1. Získejte umístění původního hlavního klíče sloupce a šifrovaných hodnot odpovídajících šifrovacích klíčů sloupců chráněných starým hlavním klíčem sloupce z dbA. | Není k dispozici Podívejte se na následující příklady. |
Ne | Ne |
| Krok 2. Vytvořte nový klíč hlavního sloupce v úložišti klíčů. Poznámka: modul SqlServer tento krok nepodporuje. Pokud chcete tento úkol provést z příkazového řádku, musíte použít nástroje, které jsou specifické pro konkrétní typ úložiště klíčů. Při použití služby Azure Key Vault jako úložiště klíčů se nepodporuje rotace klíčů spravovaných zákazníkem v prostředí s více tenanty. Ujistěte se, že nový zákazníkem spravovaný klíč je ve stejném tenantovi jako existující klíč. |
Vytvoření a uložení hlavních klíčů sloupců pro funkci Always Encrypted | Ano | Ne |
| Krok 3. Spusťte prostředí PowerShell a naimportujte modul SqlServer. | Import modulu SqlServer | Ne | Ne |
| Krok 4. Vytvořte objekt SqlColumnMasterKeySettings, který obsahuje informace o umístění vašeho starého hlavního klíče sloupce. SqlColumnMasterKeySettings je objekt, který existuje v paměti (v PowerShellu). | Nová nastavení hlavního klíče sloupce SQL | Ne | Ne |
| Krok 5. Vytvořte objekt SqlColumnMasterKeySettings, který obsahuje informace o umístění hlavní klíč nového sloupce. SqlColumnMasterKeySettings je objekt, který existuje v paměti (v PowerShellu). Pokud ho chcete vytvořit, musíte použít rutinu, která je specifická pro vaše úložiště klíčů. |
New-SqlAzureKeyVaultColumnMasterKeySettings Nové nastavení hlavního klíče sloupce úložiště certifikátů SQL New-SqlCngColumnMasterKeySettings Nové nastavení hlavního klíče sloupce SQL CNG Nové nastavení klíče ColumnMasterKey pro SqlCsp |
Ne | Ne |
| Krok 6. Ověřte se v Azure, pokud je váš starý (aktuální) hlavní klíč sloupce nebo nový hlavní klíč sloupce uložený v trezoru klíčů nebo v řízeném HSM ve službě Azure Key Vault. | Connect-AzAccount | Ano | Ne |
| Krok 7. Získejte přístupový token pro službu Azure Key Vault, pokud je hlavní klíč sloupce uložený ve službě Azure Key Vault. | Get-AzAccessToken | Ne | Ne |
| Krok 8. Znovu zašifrujte každou hodnotu šifrovacího klíče sloupce, která je aktuálně chráněna starým hlavním klíčem sloupce pomocí nového hlavního klíče sloupce. |
New-SqlColumnEncryptionKeyEncryptedValue Poznámka: Při volání této rutiny předejte objekty SqlColumnMasterKeySettings pro starý i nový hlavní klíč sloupce spolu s hodnotou šifrovacího klíče sloupce, která se má znovu zašifrovat. |
Ano | Ne |
| Krok 9. Sdílejte umístění nového hlavního klíče sloupce (název poskytovatele a cestu ke klíči) a také sadu nových šifrovaných hodnot klíčů pro šifrování sloupců se svým DBA. | Podívejte se na následující příklady. | Ne | Ne |
Poznámka
Důrazně doporučujeme po rotaci trvale neodstraňovat starý hlavní klíč sloupce. Místo toho byste měli zachovat původní hlavní klíč sloupce v aktuálním úložišti klíčů nebo ho archivovat na jiném zabezpečeném místě. Pokud databázi obnovíte ze záložního souboru k určitému bodu v čase před konfigurace nového hlavního klíče sloupce, budete pro přístup k datům potřebovat starý klíč.
Část 3: DBA
DbA vytvoří metadata pro nový hlavní klíč sloupce a aktualizuje metadata ovlivněných šifrovacích klíčů sloupců, aby se přidala nová sada šifrovaných hodnot. V tomto kroku dbA také koordinuje správce aplikací dotazující se na šifrovací sloupce, kteří zajistí, aby aplikace získala přístup k novému hlavnímu klíči sloupce. Jakmile jsou všechny aplikace nastaveny tak, aby používaly nový hlavní klíč sloupce, dbA odebere starou sadu šifrovaných hodnot a metadat hlavního klíče starého sloupce.
| Úkol | Článek | Přístup ke klíčům nebo úložišti klíčů ve formátu prostého textu | Přístup k databázi |
|---|---|---|---|
| Krok 1. Získejte umístění nového hlavního klíče sloupce a novou sadu šifrovaných hodnot odpovídajících šifrovacích klíčů sloupce chráněných starým hlavním klíčem sloupce od správce zabezpečení. | Podívejte se na následující příklady. | Ne | Ne |
| Krok 2. Spusťte prostředí PowerShell a naimportujte modul SqlServer. | Import modulu SqlServer | Ne | Ne |
| Krok 3. Připojte se k serveru a databázi. | Připojení k databázi | Ne | Ano |
| Krok 4. Vytvořte objekt SqlColumnMasterKeySettings, který obsahuje informace o umístění nového hlavního klíče sloupce. SqlColumnMasterKeySettings je objekt, který existuje v paměti (v PowerShellu). | Nová nastavení hlavního klíče sloupce SQL | Ne | Ne |
| Krok 5. Vytvořte metadata o novém hlavním klíči sloupce v databázi. |
New-SqlColumnMasterKey Poznámka: V rámci této rutiny vydává CREATE COLUMN MASTER KEY příkaz (Transact-SQL) pro vytvoření klíčových metadat. |
Ne | Ano |
| Krok 6. Načtěte metadata o klíčích pro šifrování sloupců, chráněných starým hlavním klíčem sloupců. | Get-SqlColumnEncryptionKey | Ne | Ano |
| Krok 7. Přidejte do metadat pro každý ovlivněný šifrovací klíč sloupce novou šifrovanou hodnotu (vytvořenou pomocí nového hlavního klíče sloupce). | Přidat-SqlColumnEncryptionKeyValue | Ne | Ano |
| Krok 8. Koordinujte se správci všech aplikací, které dotazují šifrované sloupce v databázi (a jsou chráněné starým hlavním klíčem sloupce), aby mohly zajistit, aby aplikace mohly přistupovat k novému hlavnímu klíči sloupce. | vytváření a ukládání hlavních klíčů sloupců (Always Encrypted) | Ne | Ne |
| Krok 9. Dokončete rotaci odebráním šifrovaných hodnot přidružených ke starému hlavnímu klíči sloupce z databáze. Poznámka: Před provedením tohoto kroku se ujistěte, že všechny aplikace, které se dotazují na šifrované sloupce chráněné starým hlavním klíčem sloupce, byly nakonfigurovány tak, aby používaly nový hlavní klíč sloupce. Pokud tento krok provedete předčasně, některé z těchto aplikací nemusí být schopny data dešifrovat. Tento krok odebere vazbu mezi starým hlavním klíčem sloupce a šifrovacími klíči sloupců, které chrání. |
Dokončit-SqlColumnMasterKeyRotation Alternativně můžete použít Remove-SqlColumnEncryptionKeyValue |
Ne | Ano |
| Krok 10. Odebrání metadat hlavního klíče starého sloupce z databáze | Remove-SqlColumnMasterKey | Ne | Ano |
Rotace hlavního klíče sloupce pomocí oddělení rolí (příklad certifikátu Windows)
Následující skript je kompletní příklad pro generování nového hlavního klíče sloupce, uloženého v úložišti certifikátů systému Windows jako certifikát, rotací existujícího (aktuálního) hlavního klíče sloupce a jeho nahrazení novým hlavním klíčem sloupce. Skript předpokládá, že cílová databáze obsahuje hlavní klíč sloupce s názvem CMK1 (který se má otočit), který šifruje některé šifrovací klíče sloupců.
Část 1: DBA
[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"
Část 2: Správce zabezpečení
[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"
Část 3: DBA
[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'
Výměna sloupcového šifrovacího klíče
Obměna šifrovacího klíče sloupce zahrnuje dešifrování dat ve všech sloupcích, které byly zašifrovány klíčem, jenž má být nahrazen, a opětovné zašifrování dat pomocí nového šifrovacího klíče sloupce. Tento pracovní postup obměně vyžaduje přístup ke klíčům i k databázi, a proto se nedá provádět oddělení rolí. Obměna šifrovacího klíče sloupce může trvat dlouhou dobu, pokud jsou tabulky, které obsahují sloupce zašifrované klíčem, jež se obměňuje, velké. Proto vaše organizace musí pečlivě naplánovat rotaci sloupcového šifrovacího klíče.
Šifrovací klíč sloupce můžete otočit pomocí offline nebo online přístupu. První metoda bude pravděpodobně rychlejší, ale vaše aplikace nemůžou zapisovat do ovlivněných tabulek. Druhý přístup bude pravděpodobně trvat déle, ale můžete omezit časový interval, během kterého nejsou ovlivněné tabulky dostupné pro aplikace. Další informace najdete v tématu Konfigurace šifrování sloupců pomocí Always Encrypted s PowerShellem a Set-SqlColumnEncryption.
| Úkol | Článek | Přístup ke klíčům nebo úložišti klíčů ve formátu prostého textu | Přístup k databázi |
|---|---|---|---|
| Krok 1. Spusťte prostředí PowerShell a naimportujte modul SqlServer. | Import modulu SqlServer | Ne | Ne |
| Krok 2. Připojte se k serveru a databázi. | Připojení k databázi | Ne | Ano |
| Krok 3. Autentizujte se u Azure, pokud je váš hlavní klíč sloupce (který chrání šifrovací klíč sloupce a má být rotačně obměněn) uložen v trezoru klíčů nebo v řízeném HSM v rámci služby Azure Key Vault. | Connect-AzAccount | Ano | Ne |
| Krok 4. Získejte přístupový token pro službu Azure Key Vault, pokud je hlavní klíč sloupce uložený ve službě Azure Key Vault. | Get-AzAccessToken | Ne | Ne |
| Krok 5. Vygenerujte nový šifrovací klíč sloupce, zašifrujte ho pomocí hlavního klíče sloupce a vytvořte metadata šifrovacího klíče sloupce v databázi. |
New-SqlColumnEncryptionKey Poznámka: Použijte variantu rutiny, která interně generuje a šifruje šifrovací klíč sloupce. Na pozadí tato rutina spouští příkaz CREATE COLUMN ENCRYPTION KEY (Transact-SQL) k vytvoření metadat klíče. |
Ano | Ano |
| Krok 6. Najdi všechny sloupce zašifrované pomocí starého šifrovacího klíče sloupce. | průvodce programováním objektů SMO (SQL Server Management Objects) | Ne | Ano |
| Krok 7. Vytvořte SqlColumnEncryptionSettings objekt pro každý ovlivněný sloupec. SqlColumnEncryptionSettings je objekt, který existuje v paměti (v PowerShellu). Určuje cílové schéma šifrování pro sloupec. V tomto případě by měl objekt určit, že ovlivněný sloupec má být zašifrován pomocí nového šifrovacího klíče sloupce. | New-SqlColumnEncryptionSettings | Ne | Ne |
| Krok 8. Znovu zašifrujte sloupce identifikované v kroku 5 pomocí nového šifrovacího klíče sloupce. |
Set-SqlColumnEncryption Poznámka: Tento krok může trvat dlouhou dobu. Vaše aplikace nebudou mít přístup k tabulkám prostřednictvím celé operace nebo jeho části v závislosti na přístupu (online nebo offline), který vyberete. |
Ano | Ano |
| Krok 9. Odeberte metadata pro starý sloupcový šifrovací klíč. | Odstranit-SqlColumnEncryptionKey | Ne | Ano |
Příklad – rotace šifrovacího klíče sloupce
Následující skript ukazuje rotaci šifrovacího klíče ke sloupci. Skript předpokládá, že cílová databáze obsahuje některé sloupce zašifrované šifrovacím klíčem sloupce s názvem CEK1 (který se má otočit), který je chráněný pomocí hlavního klíče sloupce s názvem CMK1 (hlavní klíč sloupce není uložený ve službě 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'
Další kroky
- Dotazování na sloupce pomocí Always Encrypted ve SQL Server Management Studio
- Vývoj aplikací pomocí funkce Always Encrypted
Viz také
- Always Encrypted
- Přehled správy klíčů pro funkci Always Encrypted
- Konfigurace Always Encrypted pomocí PowerShellu
- Rotace klíčů Always Encrypted pomocí aplikace 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)