PowerShell kullanarak Always Encrypted anahtarlarını döndürme

Şunlar için geçerlidir:SQL ServerAzure SQL VeritabanıAzure SQL Yönetilen Örneği

Bu makalede, SqlServer PowerShell modülünü kullanarak Always Encrypted anahtarlarını döndürme adımları sağlanır. Always Encrypted için SqlServer PowerShell modülünü kullanmaya başlama hakkında bilgi için bkz. PowerShell kullanarak Always Encrypted'ı yapılandırma.

Not

Microsoft, Always Encrypted PowerShell betiklerini çalıştırırken PowerShell 7 veya üzerini kullanmanızı önerir. PowerShell 7, birçok Always Encrypted senaryosu için gereken gelişmiş platformlar arası destek, daha iyi performans ve SqlServer modülü (v22+) ile en son uyumluluk sağlar.

Always Encrypted anahtarlarını döndürmek, mevcut bir anahtarı yenisiyle değiştirme işlemidir. Anahtarın gizliliği ihlal edilmişse veya kuruluşunuzun düzenli şifreleme anahtarı döndürmeyi zorunlu hale getiren ilkelerine veya uyumluluk düzenlemelerine uymak için anahtarı döndürmeniz gerekebilir.

Always Encrypted iki tür anahtar kullandığından iki üst düzey anahtar döndürme iş akışı vardır; sütun ana anahtarlarını döndürme ve sütun şifreleme anahtarlarını döndürme.

  • Sütun şifreleme anahtarı döndürme - geçerli anahtarla şifrelenmiş verilerin şifresini çözmeyi ve yeni sütun şifreleme anahtarını kullanarak verileri yeniden şifrelemeyi içerir. Sütun şifreleme anahtarını döndürmek hem anahtarlara hem de veritabanına erişim gerektirdiğinden, sütun şifreleme anahtarı döndürme işlemi yalnızca rol ayrımı olmadan gerçekleştirilebilir.
  • Sütun ana anahtarı döndürme - geçerli sütun ana anahtarıyla korunan sütun şifreleme anahtarlarının şifresini çözmeyi, yeni sütun ana anahtarını kullanarak bunları yeniden şifrelemeyi ve her iki anahtar türü için de meta verileri güncelleştirmeyi içerir. Sütun ana anahtarı döndürme işlemi rol ayrımı ile veya olmadan tamamlanabilir (SqlServer PowerShell modülü kullanılırken).

Rol Ayrımı Olmadan Sütun Ana Anahtar Döndürme

Bu bölümde sütun ana anahtarını döndürmek için açıklanan yöntem, güvenlik yöneticisi ile DBA arasında rol ayrımını desteklemez. Aşağıdaki adımlardan bazıları, fiziksel anahtarlardaki işlemleri anahtar meta verilerindeki işlemlerle birleştirir. Kuruluşunuz DevOps modelini kullanıyorsa veya veritabanınız bulutta barındırılıyorsa ve birincil hedef bulut yöneticilerinin (şirket içi DTA'ların değil) hassas verilere erişmesini kısıtlamaksa bu iş akışını kullanın. Olası saldırganlar DTA'lar içeriyorsa veya DTA'ların hassas verilere erişimi olmaması gerekiyorsa bu yöntemi kullanmayın.

Görev Makale Düz metin anahtarlarına/anahtar deposuna erişir Veritabanına erişir
Adım 1. Anahtar deposunda yeni bir sütun ana anahtarı oluşturun.

Not: SqlServer PowerShell modülü bu adımı desteklemez. Bu görevi komut satırından gerçekleştirmek için anahtar deponuza özgü araçları kullanmanız gerekir. Anahtar deposu olarak Azure Key Vault kullanılırken, çok kiracılı müşteri tarafından yönetilen anahtar döndürme desteklenmez. Yeni müşteri tarafından yönetilen anahtarın mevcut anahtarla aynı kiracıda olduğundan emin olun.
Always Encrypted için sütun ana anahtarları oluşturma ve depolama Evet Hayır
Adım 2. PowerShell ortamını başlatma ve SqlServer modülünü içeri aktarma SqlServer modülünü içeri aktarma Hayır Hayır
Adım 3. Sunucunuza ve veritabanınıza bağlanın. Veritabanına bağlanma Hayır Evet
Adım 4. Yeni sütun ana anahtarınızın konumu hakkında bilgi içeren bir SqlColumnMasterKeySettings nesnesi oluşturun. SqlColumnMasterKeySettings, bellekte (PowerShell'de) bulunan bir nesnedir. Bunu oluşturmak için anahtar deponuza özgü cmdlet'i kullanmanız gerekir. New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Hayır Hayır
Adım 5. Veritabanınızda yeni sütun ana anahtarınız hakkındaki meta verileri oluşturun. New-SqlColumnMasterKey

Not: Bu cmdlet, kapakların altında anahtar meta verileri oluşturmak için (Transact-SQL) deyimini düzenlerCREATE COLUMN MASTER KEY.
Hayır Evet
Adım 6. Geçerli sütun ana anahtarınız veya yeni sütun ana anahtarınız bir anahtar kasasında veya Azure Key Vault'ta yönetilen bir HSM'de depolanıyorsa Azure'da kimlik doğrulaması yapma Connect-AzAccount Evet Hayır
Adım 7. Sütun ana anahtarınız Azure Key Vault'ta depolanıyorsa Azure Key Vault için bir erişim belirteci alın. Get-AzAccessToken Hayır Hayır
8. Adım. Şu anda eski sütun ana anahtarıyla korunan sütun şifreleme anahtarlarının her birini yeni sütun ana anahtarını kullanarak şifreleyerek döndürmeyi başlatın. Bu adımdan sonra, etkilenen her sütun şifreleme anahtarı (eski sütun ana anahtarıyla ilişkili, döndürülür) hem eski hem de yeni sütun ana anahtarıyla şifrelenir ve veritabanı metaverinde iki şifrelenmiş değer bulunur. Invoke-SqlColumnMasterKeyRotation Evet Evet
Adım 9. Veritabanında şifrelenmiş sütunları sorgulayan (ve eski sütun ana anahtarıyla korunan) tüm uygulamaların yöneticileriyle eşgüdüm sağlayın; böylece uygulamaların yeni sütun ana anahtarına erişebildiğinden emin olabilirler. Sütun Ana Anahtarları Oluşturma ve Depolama (Always Encrypted) Evet Hayır
Adım 10. Döndürmeyi tamamla

Not: Bu adımı yürütmeden önce, eski sütun ana anahtarıyla korunan şifrelenmiş sütunları sorgulayan tüm uygulamaların yeni sütun ana anahtarını kullanacak şekilde yapılandırıldığından emin olun. Bu adımı erken gerçekleştirirseniz, bu uygulamalardan bazıları verilerin şifresini çözemeyebilir. Eski sütun ana anahtarıyla oluşturulan veritabanından şifrelenmiş değerleri kaldırarak döndürmeyi tamamlayın. Bu, eski sütun ana anahtarı ile koruduğu sütun şifreleme anahtarları arasındaki ilişkiyi kaldırır.
Complete-SqlColumnMasterKeyRotation Hayır Evet
11. Adım Meta verileri eski sütun ana anahtarından kaldırın. Remove-SqlColumnMasterKey Hayır Evet

Not

Döndürmeden sonra eski sütun ana anahtarını kalıcı olarak silmemenizi kesinlikle öneririz. Bunun yerine, eski sütun ana anahtarını geçerli anahtar deposunda tutmalı veya başka bir güvenli yerde arşivlemelisiniz. Veritabanınızı yedekleme dosyasından yeni sütun ana anahtarı yapılandırılmadan önceki bir noktaya geri yüklerseniz, verilere erişmek için eski anahtara ihtiyacınız olur.

Rol Ayrımı Olmadan Sütun Ana Anahtarını Döndürme (Windows Sertifikası Örneği)

Aşağıdaki betik, mevcut bir sütun ana anahtarını (CMK1) yeni bir sütun ana anahtarıyla (CMK2) değiştiren uçtan uca bir örnektir.

[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.'

Rol Ayrımı ile Sütun Ana Anahtarı Döndürme

Bu bölümde açıklanan sütun ana anahtar döndürme iş akışı, Güvenlik Yöneticisi ile DBA arasındaki ayrımı güvence altına alır.

Önemli

Aşağıdaki tabloda (düz metin anahtarlarına veya anahtar deposuna erişen adımlar) Düz metin anahtarlarına/anahtar deposuna=Evet'e eriştiği herhangi bir adımı yürütmeden önce, PowerShell ortamının veritabanınızı barındıran bir bilgisayardan farklı güvenli bir makinede çalıştığından emin olun. Daha fazla bilgi için bkz. Anahtar Yönetimi için Güvenlik Konuları.

Bölüm 1: DBA

DBA, döndürülecek sütun ana anahtarı ve mevcut sütun ana anahtarıyla ilişkili etkilenen sütun şifreleme anahtarları hakkında meta verileri alır. DBA tüm bu bilgileri bir Güvenlik Yöneticisi ile paylaşır.

Görev Makale Düz metin anahtarlarına/anahtar deposuna erişir Veritabanına erişir
Adım 1. Bir PowerShell ortamı başlatın ve SqlServer modülünü içeri aktarın. SqlServer modülünü içeri aktarma Hayır Hiç kimse
Adım 2. Sunucunuza ve bir veritabanına bağlanın. Veritabanına bağlanma Hayır Evet
Adım 3. Eski sütun ana anahtarı hakkındaki meta verileri alın. Get-SqlColumnMasterKey Hayır Evet
Adım 4. Şifrelenmiş değerleri de dahil olmak üzere eski sütun ana anahtarı tarafından korunan sütun şifreleme anahtarları hakkındaki meta verileri alın. Get-SqlColumnEncryptionKey Hayır Evet
Adım 5. Sütun ana anahtarının konumunu (sağlayıcı adı ve sütun ana anahtarının anahtar yolu) ve eski sütun ana anahtarıyla korunan ilgili sütun şifreleme anahtarlarının şifrelenmiş değerlerini paylaşın. Aşağıdaki örneklere bakın. Hayır Hayır

Bölüm 2: Güvenlik Yöneticisi

Güvenlik Yöneticisi yeni bir sütun ana anahtarı oluşturur, etkilenen sütun şifreleme anahtarlarını yeni sütun ana anahtarıyla yeniden şifreler ve yeni sütun ana anahtarı ile etkilenen sütun şifreleme anahtarları için yeni şifrelenmiş değerler setini DBA ile paylaşır.

Görev Makale Düz metin anahtarlara/anahtar deposuna erişme Veritabanına erişir
Adım 1. DBA'nızdan eski sütun ana anahtarının konumunu ve eski sütun ana anahtarıyla korunan ilgili sütun şifreleme anahtarlarının şifrelenmiş değerlerini alın. YOK
Aşağıdaki örneklere bakın.
Hayır Hayır
Adım 2. Anahtar deposunda yeni bir sütun ana anahtarı oluşturun.

Not: SqlServer modülü bu adımı desteklemez. Bu görevi bir komut satırından gerçekleştirmek için, anahtar deponuzun türüne özgü araçları kullanmanız gerekir. Anahtar deposu olarak Azure Key Vault kullanılırken, çok kiracılı müşteri tarafından yönetilen anahtar döndürme desteklenmez. Yeni müşteri tarafından yönetilen anahtarın mevcut anahtarla aynı kiracıda olduğundan emin olun.
Always Encrypted için sütun ana anahtarları oluşturma ve depolama Evet Hayır
Adım 3. Bir PowerShell ortamı başlatın ve SqlServer modülünü içeri aktarın. SqlServer modülünü içeri aktarma Hayır Hayır
Adım 4. Eski sütun ana anahtarınızın konumu hakkında bilgi içeren bir SqlColumnMasterKeySettings nesnesi oluşturun. SqlColumnMasterKeySettings, bellekte (PowerShell'de) bulunan bir nesnedir. New-SqlColumnMasterKeySettings Hayır Hayır
Adım 5. Yeni sütun ana anahtarınızın konumu hakkında bilgi içeren bir SqlColumnMasterKeySettings nesnesi oluşturun. SqlColumnMasterKeySettings, bellekte (PowerShell'de) bulunan bir nesnedir. Bunu oluşturmak için anahtar deponuza özgü cmdlet'i kullanmanız gerekir. New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Hayır Hayır
Adım 6. Eski (geçerli) sütun ana anahtarınız veya yeni sütun ana anahtarınız bir anahtar kasasında veya Azure Key Vault'ta yönetilen bir HSM'de depolanıyorsa Azure'da kimlik doğrulaması yapma. Connect-AzAccount Evet Hayır
Adım 7. Sütun ana anahtarınız Azure Key Vault'ta depolanıyorsa Azure Key Vault için bir erişim belirteci alın. Get-AzAccessToken Hayır Hayır
8. Adım. Yeni sütun ana anahtarını kullanarak şu anda eski sütun ana anahtarıyla korunan sütun şifreleme anahtarının her değerini yeniden şifreleyin. New-SqlColumnEncryptionKeyEncryptedValue

Not: Bu cmdlet'i çağırırken, hem eski hem de yeni sütun ana anahtarı için SqlColumnMasterKeySettings nesnelerini ve sütun şifreleme anahtarının değerini yeniden şifreleyin.
Evet Hayır
Adım 9. Yeni sütun ana anahtarının konumunu (sağlayıcı adı ve sütun ana anahtarının anahtar yolu) ve sütun şifreleme anahtarlarının yeni şifrelenmiş değerleri kümesini DBA'nızla paylaşın. Aşağıdaki örneklere bakın. Hayır Hayır

Not

Döndürmeden sonra eski sütun ana anahtarını kalıcı olarak silmemenizi kesinlikle öneririz. Bunun yerine, eski sütun ana anahtarını geçerli anahtar deposunda tutmalı veya başka bir güvenli yerde arşivlemelisiniz. Veritabanınızı yedekleme dosyasından yeni sütun ana anahtarı yapılandırılmadan önceki bir noktaya geri yüklerseniz, verilere erişmek için eski anahtara ihtiyacınız olur.

Bölüm 3: DBA

DBA, yeni sütun ana anahtarı için meta veri oluşturur ve etkilenen sütun şifreleme anahtarlarının meta verilerini güncellerek yeni şifrelenmiş değer setini ekler. Bu adımda DBA, uygulamanın yeni sütun ana anahtarına erişebildiğinden emin olmak için şifreleme sütunlarını sorgulayan uygulamaların yöneticileriyle de eşgüdüm sağlar. Tüm uygulamalar yeni sütun ana anahtarını kullanacak şekilde ayarlandıktan sonra, DBA eski şifrelenmiş değerler kümesini ve eski sütun ana anahtarı meta verilerini kaldırır.

Görev Makale Düz metin anahtarlara/anahtar deposuna erişme Veritabanına erişir
Adım 1. Güvenlik Yöneticinizden yeni sütun ana anahtarının konumunu ve eski sütun ana anahtarıyla korunan ilgili sütun şifreleme anahtarlarının yeni şifrelenmiş değerleri kümesini alın. Aşağıdaki örneklere bakın. Hayır Hayır
Adım 2. Bir PowerShell ortamı başlatın ve SqlServer modülünü içeri aktarın. SqlServer modülünü içeri aktarma Hayır Hayır
Adım 3. Sunucunuza ve bir veritabanına bağlanın. Veritabanına bağlanma Hayır Evet
Adım 4. Yeni sütun ana anahtarınızın konumu hakkında bilgi içeren bir SqlColumnMasterKeySettings nesnesi oluşturun. SqlColumnMasterKeySettings, bellekte (PowerShell'de) bulunan bir nesnedir. New-SqlColumnMasterKeySettings Hayır Hayır
Adım 5. Veritabanınızda yeni sütun ana anahtarınız hakkındaki meta verileri oluşturun. New-SqlColumnMasterKey

Not: Bu cmdlet, anahtar meta verilerini oluşturmak için arka planda CREATE COLUMN MASTER KEY (Transact-SQL) deyimini çalıştırır.
Hayır Evet
Adım 6. Eski sütun ana anahtarıyla korunan sütun şifreleme anahtarları hakkındaki meta verileri alın. Get-SqlColumnEncryptionKey Hayır Evet
Adım 7. Her etkilenen sütun şifreleme anahtarı için metaveriye yeni bir şifrelenmiş değer (yeni sütun ana anahtarı kullanılarak üretilen) ekleyin. Add-SqlColumnEncryptionKeyValue Hayır Evet
8. Adım. Veritabanında şifrelenmiş sütunları sorgulayan (ve eski sütun ana anahtarıyla korunan) tüm uygulamaların yöneticileriyle eşgüdüm sağlayın; böylece uygulamaların yeni sütun ana anahtarına erişebildiğinden emin olabilirler. Sütun Ana Anahtarlarını Oluşturma ve Depolama (Always Encrypted) Hayır Hayır
Adım 9. Eski sütun ana anahtarıyla ilişkili şifrelenmiş değerleri veritabanından kaldırarak döndürme işlemini tamamlayın.

Not: Bu adımı yürütmeden önce, eski sütun ana anahtarıyla korunan şifrelenmiş sütunları sorgulayan tüm uygulamaların yeni sütun ana anahtarını kullanacak şekilde yapılandırıldığından emin olun. Bu adımı erken gerçekleştirirseniz, bu uygulamalardan bazıları verilerin şifresini çözemeyebilir.

Bu adım, eski sütun ana anahtarı ile koruduğu sütun şifreleme anahtarları arasındaki ilişkiyi kaldırır.
Complete-SqlColumnMasterKeyRotation

Alternatif olarak Remove-SqlColumnEncryptionKeyValue kullanabilirsiniz
Hayır Evet
Adım 10. Eski sütun ana anahtarı meta verilerini veritabanından kaldırma Remove-SqlColumnMasterKey Hayır Evet

Rol Ayrımı ile Sütun Ana Anahtarını Döndürme (Windows Sertifikası Örneği)

Aşağıdaki betik, Windows Sertifika deposunda sertifika olan yeni bir sütun ana anahtarı oluşturmaya, mevcut (geçerli) bir sütun ana anahtarını döndürerek yeni sütun ana anahtarıyla değiştirmek için uçtan uca bir örnektir. Betik, hedef veritabanının bazı sütun şifreleme anahtarlarını şifreleyen CMK1 adlı (döndürülecek) sütun ana anahtarını içerdiği varsayılır.

Bölüm 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"

Bölüm 2: Güvenlik Yöneticisi

[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"

Bölüm 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'

Sütun Şifreleme Anahtarını Döndürme

Sütun şifreleme anahtarını döndürmek, tüm sütunlardaki verilerin şifresini çözmeyi, döndürülecek anahtarla şifrelenmesini ve yeni sütun şifreleme anahtarını kullanarak verileri yeniden şifrelemeyi içerir. Bu döndürme iş akışı hem anahtarlara hem de veritabanına erişim gerektirir ve bu nedenle rol ayrımı ile gerçekleştirilemez. Sütun şifreleme anahtarının döndürülmesi, döndürülen anahtarla şifrelenmiş sütunları içeren tablolar büyükse uzun sürebilir. Bu nedenle kuruluşunuzun bir sütun şifreleme anahtarı döndürmesini dikkatle planlaması gerekir.

Çevrimdışı veya çevrimiçi bir yaklaşım kullanarak sütun şifreleme anahtarını döndürebilirsiniz. İlk yöntem muhtemelen daha hızlı olur, ancak uygulamalarınız etkilenen tablolara yazamaz. İkinci yaklaşım muhtemelen daha uzun sürecektir, ancak etkilenen tabloların uygulamalara açık olmadığı zaman aralığını sınırlayabilirsiniz. Daha fazla bilgi için bkz. PowerShell ve Set-SqlColumnEncryptionile Always Encrypted kullanarak sütun şifrelemesini yapılandırma.

Görev Makale Düz metin anahtarlarına/anahtar deposuna erişir Veritabanına erişir
Adım 1. Bir PowerShell ortamı başlatın ve SqlServer modülünü içeri aktarın. SqlServer modülünü içeri aktarma Hayır Hayır
Adım 2. Sunucunuza ve bir veritabanına bağlanın. Veritabanına bağlanma Hayır Evet
Adım 3. Sütun ana anahtarınız (döndürülecek sütun şifreleme anahtarını korumak) Azure Key Vault'ta bir anahtar kasasında veya yönetilen bir HSM'de depolanıyorsa Azure'da kimlik doğrulaması yapma. Connect-AzAccount Evet Hayır
Adım 4. Sütun ana anahtarınız Azure Key Vault'ta depolanıyorsa Azure Key Vault için bir erişim belirteci alın. Get-AzAccessToken Hayır Hayır
Adım 5. Yeni bir sütun şifreleme anahtarı oluşturun, bunu sütun ana anahtarıyla şifreleyin ve veritabanında sütun şifreleme anahtarı meta verileri oluşturun. New-SqlColumnEncryptionKey

Not: Dahili olarak bir sütun şifreleme anahtarı oluşturan ve şifreleyen cmdlet'in bir çeşitlemesi kullanın.
Bu cmdlet, arka planda anahtar meta verilerini oluşturmak için CREATE COLUMN ENCRYPTION KEY (Transact-SQL) deyimini yürütür.
Evet Evet
Adım 6. Eski sütun şifreleme anahtarıyla şifrelenmiş tüm sütunları bulun. SQL Server Yönetim Nesneleri (SMO) Programlama Kılavuzu Hayır Evet
Adım 7. Her etkilenen sütun için bir SqlColumnEncryptionSettings nesnesi oluşturun. SqlColumnEncryptionSettings bellekte (PowerShell'de) bulunan bir nesnedir. Bir sütun için hedef şifreleme düzenini belirtir. Bu durumda, nesne etkilenen sütunun yeni sütun şifreleme anahtarı kullanılarak şifrelenmesi gerektiğini belirtmelidir. New-SqlColumnEncryptionSettings Hayır Hayır
8. Adım. Yeni sütun şifreleme anahtarını kullanarak 5. adımda tanımlanan sütunları yeniden şifreleyin. Set-SqlColumnEncryption

Not: Bu adım uzun sürebilir. Uygulamalarınız, seçtiğiniz yaklaşıma (çevrimiçi veya çevrimdışı) bağlı olarak tüm işlem veya bir bölümü aracılığıyla tablolara erişemez.
Evet Evet
Adım 9. Eski sütun şifreleme anahtarının meta verilerini kaldırın. Remove-SqlColumnEncryptionKey Hayır Evet

Örnek - Sütun Şifreleme Anahtarını Döndürme

Aşağıdaki betik, bir sütun şifreleme anahtarını döndürmeyi gösterir. Betik, hedef veritabanında bazı sütunların CEK1 adlı bir sütun şifreleme anahtarıyla şifrelendiğini varsayar. CEK1, döndürülecek olan ve CMK1 adlı bir sütun ana anahtarı kullanılarak korunan bir anahtardır. Sütun ana anahtarı, Azure Key Vault'ta depolanmamaktadır.

[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'