Always Encrypted-kulcsok kiépítése a PowerShell használatával

A következőkre vonatkozik:SQL ServerAzure SQL DatabaseFelügyelt Azure SQL-példány

Ez a cikk az Always Encrypted kulcsainak az SqlServer PowerShell-modullal történő kiépítésének lépéseit ismerteti. A PowerShell használatával az Always Encrypted kulcsokat létrehozhatja mind szerepkör-elkülönítéssel, mind szerepkör-elkülönítés nélkül, szabályozva, hogy ki férhet hozzá a kulcstárolóban lévő tényleges titkosítási kulcsokhoz, és ki férhet hozzá az adatbázishoz.

Megjegyzés

Microsoft a PowerShell 7 vagy újabb verzióját javasolja Always Encrypted PowerShell-szkriptek futtatásakor. A PowerShell 7 továbbfejlesztett platformfüggetlen támogatást, jobb teljesítményt és a legújabb kompatibilitást biztosít az SqlServer modullal (v22+), amely számos Always Encrypted-forgatókönyvhöz szükséges.

Az Always Encrypted kulcskezelésének áttekintéséért, beleértve néhány magas szintű ajánlott eljárásra vonatkozó javaslatot, tekintse meg az Always Encrypted kulcskezelésének áttekintését. Az Always Encryptedhez készült SqlServer PowerShell-modul használatának megkezdéséről további információt az Always Encrypted konfigurálása a PowerShell használatával című témakörben talál.

Kulcskiosztás szerepkör-elkülönítés nélkül

Az ebben a szakaszban ismertetett kulcskiépítési módszer nem támogatja a biztonsági rendszergazdák és a dbA-k közötti szerepkör-elkülönítést. A szakasz néhány lépése a fizikai kulcsokon végzett műveleteket kombinálja a kulcs metaadatain végzett műveletekkel. Ezért akkor használja ezt a módszert a kulcsok kiépítésére, ha a szervezet a DevOps-modellt használja, vagy ha az adatbázis a felhőben van üzemeltetve, és az elsődleges cél a felhőgazdák (de nem a helyszíni adatbázis-üzemeltetők) hozzáférésének korlátozása a bizalmas adatokhoz. Ne használja ezt a módszert, ha a lehetséges támadók adatbázis-kezelőket is tartalmazhatnak, vagy ha a dbA-k nem férhetnek hozzá bizalmas adatokhoz.

Az egyszerű szöveges kulcsokhoz vagy a kulcstárolóhoz való hozzáféréssel járó lépések futtatása előtt (amelyet az Accesses egyszerű szöveges kulcsok/kulcstároló oszlopban talál a következő táblázatban) győződjön meg arról, hogy a PowerShell-környezet egy olyan biztonságos gépen fut, amely eltér az adatbázist futtató számítógéptől. További információ: A kulcskezelés biztonsági szempontjai.

Feladat Cikk Egyszerű szöveges kulcsok/kulcstárolók elérése Adatbázis elérése
1. lépés. Hozzon létre egy oszlop főkulcsát egy kulcstárolóban.

Jegyzet: Az SqlServer PowerShell-modul nem támogatja ezt a lépést. Ha parancssorból szeretné elvégezni ezt a feladatot, használja a kiválasztott kulcstárolóhoz tartozó eszközöket.
Oszlop főkulcsainak létrehozása és tárolása az Always Encryptedhez Igen Nem
2. lépés. Indítsa el a PowerShell-környezetet, és importálja az SqlServer PowerShell-modult. Always Encrypted konfigurálása a PowerShell használatával Nem Nem
3. lépés. Csatlakozzon a kiszolgálóhoz és az adatbázishoz. Csatlakozás adatbázishoz Nem Igen
4. lépés. Hozzon létre egy SqlColumnMasterKeySettings objektumot, amely információkat tartalmaz az oszlop főkulcsának helyéről. Az SqlColumnMasterKeySettings egy olyan objektum, amely a memóriában (a PowerShellben) létezik. Használja a kulcstárolóra jellemző parancsmagot. Új-SqlAzureKeyVaultOszlopFőKulcsBeállítások

Új-SqlTanúsítványTárolóOszlopFőkulcsBeállítások

Új-SqlCngOszlopMesterKulcsBeállítások

Új-SqlCspOszlopMesterKulcsBeállítások
Nem Nem
5. lépés. Hozza létre az oszlop főkulcsának metaadatait az adatbázisban.

Jegyzet: Nem ellenőrizzük az oszlop főkulcsának létrehozásához használt kulcsok vagy tanúsítványok érvényességét.
[New-SqlColumnMasterKey](/powershell/sqlserver/sqlserver/vlatest/new-sqlcolumnmasterkey

Megjegyzés: A borítók alatt a parancsmag kiadja a CREATE COLUMN MASTER KEY kulcs metaadatainak létrehozásához tartozó utasítást.
Nem Igen
6. lépés. Hitelesítés az Azure-ban, ha az oszlop főkulcsa az Azure Key Vaultban van tárolva. Connect-AzAccount Igen Nem
7. lépés. Szerezze be az Azure kulcstárak hozzáférési tokenjét, ha az oszlop főkulcsa az Azure kulcstárban van tárolva. Get-AzAccessToken Nem Nem
8. lépés. Hozzon létre egy új oszloptitkosítási kulcsot, titkosítsa az oszlop főkulcsával, és hozzon létre oszloptitkosítási kulcs metaadatait az adatbázisban. New-SqlColumnEncryptionKey

Jegyzet: Használja a parancsmag egy olyan változatát, amely belsőleg létrehoz és titkosít egy oszloptitkosítási kulcsot.

Megjegyzés: A borítók alatt a parancsmag kiadja az utasítást a CREATE COLUMN ENCRYPTION KEY kulcs metaadatainak létrehozásához.
Igen Igen

Windows Tanúsítványtár szerepkör-elkülönítés nélkül (példa)

Ez a szkript egy végponttól végpontig példa egy olyan oszlop főkulcsának létrehozására, amely egy tanúsítvány a Windows Tanúsítványtárolóban, oszloptitkosítási kulcs létrehozására és titkosítására, valamint kulcs metaadatok létrehozására egy SQL Server-adatbázisban.

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$DatabaseName = '<database name>',

	[Parameter(Mandatory = $false)]
	[string]$ServerName = "<server name>",

	[Parameter(Mandatory = $false)]
	[string]$CertificateSubject = "AlwaysEncryptedCert",

	[Parameter(Mandatory = $false)]
	[string]$CmkName = "CMK",

	[Parameter(Mandatory = $false)]
	[string]$CekName = "CEK"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop

Write-Host "[AE] Locating certificate '$CertificateSubject' in CurrentUser\\My"
$cert = Get-ChildItem -Path Cert:CurrentUser\My |
	Where-Object { $_.Subject -eq "CN=$CertificateSubject" } |
	Sort-Object NotAfter -Descending |
	Select-Object -First 1

if (-not $cert) {
	Write-Host "[AE] Certificate not found. Creating self-signed certificate."
	$cert = New-SelfSignedCertificate `
		-Subject $CertificateSubject `
		-CertStoreLocation Cert:CurrentUser\My `
		-KeyExportPolicy Exportable `
		-Type DocumentEncryptionCert `
		-KeyUsage DataEncipherment `
		-KeySpec KeyExchange
}

Write-Host "[AE] 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' database '$DatabaseName'. Verify instance name SQL2025, database existence, and local permissions."
	throw
}

Write-Host "[AE] Creating CMK settings from certificate thumbprint"
$cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation "CurrentUser" -Thumbprint $cert.Thumbprint

Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK '$CekName' exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}

Write-Host "Completed successfully"

Azure Key Vault szerepkör-elkülönítés nélkül (példa)

Ez a szkript egy végpontok közötti példa egy kulcstartó kiépítésére és konfigurálására az Azure Key Vaultban, egy oszlop-főkulcs létrehozása a tárolóban, oszloptitkosítási kulcs létrehozása és titkosítása, valamint kulcs metaadatainak létrehozása egy Azure SQL-adatbázisban.

param(
	[Parameter(Mandatory = $true)] [string]$SubscriptionId,
	[Parameter(Mandatory = $true)] [string]$ResourceGroupName,
	[Parameter(Mandatory = $true)] [string]$AzureLocation,
	[Parameter(Mandatory = $true)] [string]$KeyVaultName,
	[Parameter(Mandatory = $true)] [string]$KeyName,
	[Parameter(Mandatory = $true)] [string]$ServerName,
	[Parameter(Mandatory = $true)] [string]$DatabaseName,
	[string]$CmkName = "CMK",
	[string]$CekName = "CEK",
	[bool]$AssignRbacToCurrentPrincipal = $true
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

Import-Module Az.Accounts -ErrorAction Stop
Import-Module Az.Resources -ErrorAction Stop
Import-Module Az.KeyVault -ErrorAction Stop
Import-Module SqlServer -ErrorAction Stop

function Get-CurrentPrincipalObjectId {
	param([string]$AccountId)

	$userSignedIn = Get-AzADUser -SignedIn -ErrorAction SilentlyContinue
	if ($userSignedIn) { return $userSignedIn.Id }

	$user = Get-AzADUser -UserPrincipalName $AccountId -ErrorAction SilentlyContinue
	if ($user) { return $user.Id }

	$sp = Get-AzADServicePrincipal -DisplayName $AccountId -ErrorAction SilentlyContinue | Select-Object -First 1
	if ($sp) { return $sp.Id }

	throw "Could not resolve Microsoft Entra object id for account '$AccountId'."
}

try {
	Write-Host "[AE] Signing in and selecting subscription"
	Connect-AzAccount | Out-Null
	$ctx = Set-AzContext -SubscriptionId $SubscriptionId

	Write-Host "[AE] Ensuring resource group exists"
	$resourceGroup = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
	if (-not $resourceGroup) {
		$resourceGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation
	}

	Write-Host "[AE] Ensuring key vault exists (RBAC mode)"
	$vault = Get-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue
	if (-not $vault) {
		$vault = New-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -Location $AzureLocation -EnableRbacAuthorization
	}

	if (-not $vault.EnableRbacAuthorization) {
		throw "Key Vault '$KeyVaultName' is not using RBAC authorization. Enable RBAC authorization on the vault before running this script."
	}

	if ($AssignRbacToCurrentPrincipal) {
		Write-Host "[AE] Ensuring RBAC role assignment"
		$principalSignInName = $ctx.Account.Id
		$roleName = "Key Vault Crypto Officer"
		$existingRole = Get-AzRoleAssignment -SignInName $principalSignInName -Scope $vault.ResourceId -RoleDefinitionName $roleName -ErrorAction SilentlyContinue
		if (-not $existingRole) {
			New-AzRoleAssignment -SignInName $principalSignInName -Scope $vault.ResourceId -RoleDefinitionName $roleName | Out-Null
		}
	}

	Write-Host "[AE] Ensuring column master key material exists in Key Vault"
	$akvKey = Get-AzKeyVaultKey -VaultName $KeyVaultName -Name $KeyName -ErrorAction SilentlyContinue
	if (-not $akvKey) {
		$akvKey = Add-AzKeyVaultKey -VaultName $KeyVaultName -Name $KeyName -Destination "Software"
	}

	Write-Host "[AE] Connecting to Azure SQL and creating metadata"
	$keyVaultAccessToken = (Get-AzAccessToken -ResourceUrl "https://vault.azure.net").Token
	$connStr = "Server=tcp:$ServerName.database.windows.net,1433;Database=$DatabaseName;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;Authentication=Active Directory Interactive"
	$database = Get-SqlDatabase -ConnectionString $connStr -Encrypt Mandatory
	$cmkSettings = New-SqlAzureKeyVaultColumnMasterKeySettings -KeyUrl $akvKey.Key.Kid

	$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
	if (-not $existingCmk) {
		New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
	}

	$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
	if (-not $existingCek) {
		New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName -KeyVaultAccessToken $keyVaultAccessToken | Out-Null
	}

	Write-Host "Completed successfully"
}
catch {
	Write-Error "Script failed: $($_.Exception.Message)"
	throw
}

CNG/KSP szerepkör szétválasztása nélkül (példa)

Az alábbi szkript egy végpontok közötti példa egy oszlop főkulcsának létrehozására egy kulcstárban, amely implementálja a titkosítási next generation API-t (CNG), létrehoz és titkosít egy oszloptitkosítási kulcsot, és kulcs metaadatokat hoz létre egy SQL Server-adatbázisban.

A példa a Microsoft Szoftverkulcstár-szolgáltatót használó kulcstárolót használja. Módosíthatja a példát úgy, hogy egy másik tárolót használjon, például a hardveres biztonsági modult. Ehhez meg kell győződnie arról, hogy a CNG-t az eszközön megvalósító kulcstároló-szolgáltató (KSP) telepítve van és megfelelően működik a gépen. A Microsoft Software Key Storage Provider-t le kell cserélnie az eszköz KSP-nevével.

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$ServerName = "<server name>",

	[Parameter(Mandatory = $true)]
	[string]$DatabaseName = "<database name>",

	[Parameter(Mandatory = $false)]
	[string]$CngKeyName = "AlwaysEncryptedKey",

	[Parameter(Mandatory = $false)]
	[string]$CmkName = "CMK",

	[Parameter(Mandatory = $false)]
	[string]$CekName = "CEK"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

# Local key store provider and key settings.
$cngProviderName = "Microsoft Software Key Storage Provider"
$cngAlgorithmName = "RSA"
$cngKeySize = 2048

Import-Module SqlServer -ErrorAction Stop

Write-Host "[AE] Creating local CNG key '$CngKeyName'"
$cngProvider = New-Object System.Security.Cryptography.CngProvider($cngProviderName)
$cngKeyParams = New-Object System.Security.Cryptography.CngKeyCreationParameters
$cngKeyParams.Provider = $cngProvider
$cngKeyParams.KeyCreationOptions = [System.Security.Cryptography.CngKeyCreationOptions]::OverwriteExistingKey
$keySizeProperty = New-Object System.Security.Cryptography.CngProperty(
	"Length",
	[System.BitConverter]::GetBytes($cngKeySize),
	[System.Security.Cryptography.CngPropertyOptions]::None
)
$cngKeyParams.Parameters.Add($keySizeProperty)
$cngAlgorithm = New-Object System.Security.Cryptography.CngAlgorithm($cngAlgorithmName)
[System.Security.Cryptography.CngKey]::Create($cngAlgorithm, $CngKeyName, $cngKeyParams) | Out-Null

Write-Host "[AE] Connecting to $ServerName / $DatabaseName"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;Encrypt=True;TrustServerCertificate=True"
$database = Get-SqlDatabase -ConnectionString $connStr

Write-Host "[AE] Preparing column master key settings"
$cmkSettings = New-SqlCngColumnMasterKeySettings -CngProviderName $cngProviderName -KeyName $CngKeyName

Write-Host "[AE] Ensuring CMK exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}

Write-Host "Completed successfully"

Kulcskiosztás szerepkör-elkülönítéssel

Ez a szakasz a titkosítás konfigurálását ismerteti, ha a biztonsági rendszergazdák nem férnek hozzá az adatbázishoz, és az adatbázisgazdák nem férnek hozzá a kulcstárolóhoz vagy az egyszerű szöveges kulcsokhoz.

biztonsági rendszergazda

Az egyszerű szöveges kulcsokhoz vagy a kulcstárolóhoz való hozzáféréssel járó lépések futtatása előtt (a következő táblázat Accesses egyszerű szöveges kulcsainak/kulcstároló oszlopában azonosítva) győződjön meg arról, hogy:

  • A PowerShell-környezet egy olyan biztonságos gépen fut, amely eltér az adatbázist futtató számítógéptől.
  • A szervezet adatbázis-rendszergazdái nem férnek hozzá a géphez (ez a szerepkör-elkülönítés célját nem tenné lehetővé).

További információ: A kulcskezelés biztonsági szempontjai.

Feladat Cikk Egyszerű szöveges kulcsok/kulcstárolók elérése Adatbázis elérése
1. lépés. Hozzon létre egy oszlop főkulcsát egy kulcstárolóban.

Jegyzet: Az SqlServer modul nem támogatja ezt a lépést. Ha parancssorból szeretné elvégezni ezt a feladatot, a kulcstároló típusának megfelelő eszközöket kell használnia.
Oszlop főkulcsainak létrehozása és tárolása az Always Encryptedhez Igen Nem
2. lépés. Indítsa el a PowerShell-munkamenetet, és importálja az SqlServer modult. Az SqlServer modul importálása Nem Nem
3. lépés. Hozzon létre egy SqlColumnMasterKeySettings objektumot, amely információkat tartalmaz az oszlop főkulcsának helyéről. Az SqlColumnMasterKeySettings egy olyan objektum, amely a memóriában (a PowerShellben) létezik. Használja a kulcstárolóra jellemző parancsmagot. Új-SqlAzureKeyVaultOszlopFőKulcsBeállítások

Új-SqlTanúsítványTárolóOszlopFőkulcsBeállítások

Új-SqlCngOszlopMesterKulcsBeállítások

Új-SqlCspOszlopMesterKulcsBeállítások
Nem Nem
4. lépés. Hitelesítés az Azure-ban, ha az oszlop főkulcsa az Azure Key Vaultban van tárolva. Connect-AzAccount Igen Nem
5. lépés. Szerezze be az Azure kulcstárak hozzáférési tokenjét, ha az oszlop főkulcsa az Azure kulcstárban van tárolva. Get-AzAccessToken Nem Nem
6. lépés. Hozzon létre egy oszloptitkosítási kulcsot, majd titkosítsa azt az oszlopmester-kulccsal, hogy létrejöjjön az oszloptitkosítási kulcs titkosított értéke. New-SqlColumnEncryptionKeyEncryptedValue Igen Nem
7. lépés. Adja meg az oszlop főkulcsának helyét (a szolgáltató nevét és az oszlop főkulcsának elérési útját), valamint az oszloptitkosítási kulcs titkosított értékét a DBA-nak. Tekintse meg a példákat a cikk végén. Nem Nem

DBA

A adatbázisgazdák a biztonsági rendszergazdától kapott információkat (a fenti 7. lépés) használják az Always Encrypted kulcs metaadatainak létrehozására és kezelésére az adatbázisban.

Feladat Cikk Egyszerű szöveges kulcsok elérése Adatbázis elérése
1. lépés. Szerezze be az oszlop főkulcsának helyét és az oszloptitkosítási kulcs titkosított értékét a biztonsági rendszergazdától. Tekintse meg a példákat a cikk végén. Nem Nem
2. lépés. Indítsa el a PowerShell-környezetet, és importálja az SqlServer modult. Always Encrypted konfigurálása a PowerShell használatával Nem Nem
3. lépés. Csatlakozzon a kiszolgálóhoz és egy adatbázishoz. Csatlakozás adatbázishoz Nem Igen
4. lépés. Hozzon létre egy SqlColumnMasterKeySettings objektumot, amely információkat tartalmaz az oszlop főkulcsának helyéről. Az SqlColumnMasterKeySettings egy olyan objektum, amely a memóriában létezik. Új-SqlOszlopfőkulcsBeállítások Nem Nem
5. lépés. Hozza létre az oszlop főkulcsának metaadatait az adatbázisban.

Jegyzet: Nem ellenőrizzük az oszlop főkulcsának létrehozásához használt kulcsok vagy tanúsítványok érvényességét.
New-SqlColumnMasterKey
Megjegyzés: A fedőlap alatt a parancsmag kiadja a CREATE COLUMN MASTER KEY (Transact-SQL) utasítást az oszlop főkulcs-metaadatainak létrehozásához.
Nem Igen
6. lépés. Hozza létre az oszloptitkosítási kulcs metaadatait az adatbázisban. Új-SQLOszlopTitkosításiKulcs
Jegyzet: A dbA-k a parancsmag egy olyan változatát használják, amely csak oszloptitkosítási kulcs metaadatait hozza létre.
A borítók alatt a parancsmag kiadja a CREATE COLUMN ENCRYPTION KEY (Transact-SQL) utasítást az oszloptitkosítási kulcs metaadatainak létrehozásához.
Nem Igen

Windows Tanúsítványtároló szerepkör-elkülönítéssel (példa)

biztonsági rendszergazda

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$ServerName = '<server name>',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$DatabaseName = '<database name>',

	[Parameter(Mandatory = $false)]
	[string]$CertificateSubject = 'AlwaysEncryptedCert',

	[Parameter(Mandatory = $false)]
	[string]$CmkName = 'CMK1',

	[Parameter(Mandatory = $false)]
	[string]$CekName = 'CEK1',

	[Parameter(Mandatory = $false)]
	[string]$ExportKeyDataPath = 'C:\temp\keydata.txt'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop

Write-Host "[AE] Finding certificate '$CertificateSubject' in CurrentUser\\My"
$cert = Get-ChildItem -Path 'Cert:CurrentUser\My' |
	Where-Object { $_.Subject -eq "CN=$CertificateSubject" } |
	Sort-Object NotAfter -Descending |
	Select-Object -First 1

if (-not $cert) {
	Write-Host '[AE] Certificate not found. Creating a new self-signed certificate.'
	$cert = New-SelfSignedCertificate `
		-Subject $CertificateSubject `
		-CertStoreLocation 'Cert:CurrentUser\My' `
		-KeyExportPolicy Exportable `
		-Type DocumentEncryptionCert `
		-KeyUsage DataEncipherment `
		-KeySpec KeyExchange
}

Write-Host "[AE] 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 name, database, and local permissions."
	throw
}

Write-Host '[AE] Building CMK settings from certificate'
$cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation 'CurrentUser' -Thumbprint $cert.Thumbprint

Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK '$CekName' exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}

if ($ExportKeyDataPath) {
	Write-Host "[AE] Exporting key metadata to '$ExportKeyDataPath'"
	$encryptedValue = New-SqlColumnEncryptionKeyEncryptedValue -TargetColumnMasterKeySettings $cmkSettings
	"KeyStoreProviderName,KeyPath,EncryptedValue" | Set-Content -Path $ExportKeyDataPath -Encoding UTF8
	"$($cmkSettings.KeyStoreProviderName),$($cmkSettings.KeyPath),$encryptedValue" | Add-Content -Path $ExportKeyDataPath -Encoding UTF8
}

Write-Host 'Completed successfully'

DBA

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$ServerName = 'localhost\SQL2025',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$DatabaseName = 'AdventureWorks2025',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$KeyDataFile = 'C:\temp\keydata.txt',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$CmkName = 'CMK1',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$CekName = 'CEK1'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop

if (-not (Test-Path -Path $KeyDataFile -PathType Leaf)) {
	throw "Key data file not found: $KeyDataFile"
}

Write-Host "[AE] Loading key metadata from '$KeyDataFile'"
$keyData = Import-Csv -Path $KeyDataFile
if (-not $keyData) {
	throw "Key data file '$KeyDataFile' is empty."
}

$keyDataRow = $keyData | Select-Object -First 1
if (-not $keyDataRow.KeyStoreProviderName -or -not $keyDataRow.KeyPath -or -not $keyDataRow.EncryptedValue) {
	throw "Key data file must include non-empty columns: KeyStoreProviderName, KeyPath, EncryptedValue."
}

Write-Host "[AE] 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 name, database, and local permissions."
	throw
}

Write-Host "[AE] Building CMK settings for provider '$($keyDataRow.KeyStoreProviderName)'"
$cmkSettings = New-SqlColumnMasterKeySettings -KeyStoreProviderName $keyDataRow.KeyStoreProviderName -KeyPath $keyDataRow.KeyPath

Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK '$CekName' exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName -EncryptedValue $keyDataRow.EncryptedValue | Out-Null
}

Write-Host 'Completed successfully'