Otočení klíčů Always Encrypted pomocí PowerShellu

platí pro:SQL ServerAzure SQL Databaseazure 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

Viz také