适用于:Windows
Azure SQL 数据库上的 SQL Server 2019 (15.x) 及更高版本
本文介绍如何预配已启用 enclave 的密钥,这些密钥支持用于具有安全 enclave 的 Always Encrypted 的服务器端安全 enclave 中的计算。
管理 Always Encrypted 密钥的一般准则和流程在预配已启用 enclave 的密钥时适用。 本文介绍与 Always Encrypted with secure enclaves 相关的特定细节。
若要使用 SQL Server Management Studio 或 PowerShell 预配已启用 enclave 的列主密钥,请确保新密钥支持 enclave 计算。 这会导致工具(SSMS 或 PowerShell)生成 CREATE COLUMN MASTER KEY 语句,该语句在数据库的列主密钥元数据中设置 ENCLAVE_COMPUTATIONS。 有关详细信息,请参阅CREATE COLUMN MASTER KEY(Transact-SQL)。
该工具还会使用列主密钥对列主属性进行数字签名,并将签名存储在数据库元数据中。 此签名可防止恶意篡改 ENCLAVE_COMPUTATIONS 设置。 SQL 客户端驱动程序验证签名之后才会允许使用 enclave。 这使安全管理员能够控制哪些列数据可以在安全区内进行计算。
在元数据中定义列主密钥后,该 ENCLAVE_COMPUTATIONS 属性是不可变的,并且无法更改它。 若要通过使用由现有列主密钥加密的列加密密钥来启用 enclave 计算功能,请轮换列主密钥,并将其替换为支持 enclave 的列主密钥。 请参阅轮换已启用 enclave 功能的密钥。
注意
当前,SSMS 和 PowerShell 都支持 Azure 密钥保管库或 Windows 证书存储中存储的已启用 enclave 的列主密钥。 硬件安全模块(使用CNG或CAPI)不受支持。
若要创建已启用 enclave 的列加密密钥,需要确保选择已启用 enclave 的列主密钥来加密新密钥。
以下部分提供有关如何使用 SSMS 和 PowerShell 预配已启用 enclave 的密钥的更多详细信息。
使用 SQL Server Management Studio 配置启用 enclave 的密钥
在 SQL Server Management Studio 中,可以预配:
- 已启用 enclave 的列主密钥(使用“新建列主密钥”对话框)。
- 已启用 enclave 的列加密密钥(使用“新建列加密密钥”对话框)。
借助 Always Encrypted 向导,还可以创建已启用 Enclave 的列主密钥和已启用 Enclave 的列加密密钥。
安装最新版本的 SQL Server Management Studio (SSMS)。
使用“新建列主密钥”对话框预配已启用 enclave 的列主密钥
若要预配已启用 enclave 的列主密钥,请按照使用“新建列主密钥”对话框预配列主密钥中的步骤进行操作。 确保选择“允许 enclave 计算”。 请参阅以下屏幕截图:
注意
仅在为数据库配置了安全 enclave 时,才会显示“允许 enclave 计算”复选框。 如果您使用 SQL Server,请参阅 在 SQL Server 中配置安全隔离区。 如果您使用 Azure SQL 数据库,请参阅 为 Azure SQL 数据库启用具有安全数据保护区的 Always Encrypted。
提示
若要检查列主密钥是否已启用 enclave,请在对象资源管理器中右键单击它,然后选择“属性”。 如果密钥已启用 Enclave,则显示键属性的窗口中将会显示“Enclave 计算:允许”。 或者,可以使用 sys.column_master_keys (Transact-SQL) 视图。
使用“新建列加密密钥”对话框预配已启用 enclave 的列加密密钥
若要预配已启用 enclave 的列加密密钥,请按照使用“新建列加密密钥”对话框预配列加密密钥中的步骤进行操作。 选择列主密钥时,请确保已启用 enclave。
提示
若要检查列加密密钥是否已启用 enclave,请在对象资源管理器中右键单击它,然后选择“属性”。 如果密钥已启用 Enclave,则显示键属性的窗口中将会显示“Enclave 计算:允许”。
使用 PowerShell 配置启用 enclave 的密钥
要使用 PowerShell 预配已启用 Enclave 的密钥,需要 SqlServer Powershell 模块版本 22 或更高版本。
注意
Microsoft建议在运行 Always Encrypted PowerShell 脚本时使用 PowerShell 7 或更高版本。 PowerShell 7 提供了改进的跨平台支持、更好的性能和与 SqlServer 模块(v22+)的最新兼容性,这是许多 Always Encrypted 方案所必需的。
一般而言,为 Always Encrypted 提供 PowerShell 密钥的预配工作流(在 使用 PowerShell 预配 Always Encrypted 密钥 中进行了介绍),无论是否有角色分离,也适用于 enclave 支持的密钥。 此部分介绍特定于已启用 enclave 的密钥的详细信息。
SqlServer PowerShell 模块通过 参数扩展了 New-SqlCertificateStoreColumnMasterKeySettings 和 New-SqlAzureKeyVaultColumnMasterKeySettings cmdlet,使您可以在预配过程中指定启用了 enclave 的列主密钥。 任一 cmdlet 都会创建包含列主密钥(存储在 Azure 密钥保管库 或 Windows 证书存储中)的属性的本地对象。 如果指定,则 -AllowEnclaveComputations 属性会在本地对象中将密钥标记为已启用 enclave。 它还会使 cmdlet 访问所引用的列主密钥(在 Azure 密钥保管库 或 Windows 证书存储中)以对密钥的属性进行数字签名。 为新的已启用 enclave 的列主密钥创建设置对象后,便可以在 New-SqlColumnMasterKey cmdlet 的后续调用中使用它,以在数据库中创建描述新密钥的元数据对象。
预配已启用 enclave 的列加密密钥与预配未启用 enclave 的列加密密钥没有什么不同。 只需确保用于加密新的列加密密钥的列主密钥已启用 enclave。
注意
SqlServer PowerShell 模块目前不支持预配在硬件安全模块(使用 CNG 或 CAPI)中存储的已启用 enclave 的密钥。
示例 - 使用 Windows 证书存储预配已启用 enclave 的密钥
下面的端到端示例演示如何预配已启用 enclave 的密钥(将列主密钥存储在 Windows 证书存储中)。 该脚本基于不使用角色分隔的 Windows 证书存储(示例)中的示例。 请务必注意 -AllowEnclaveComputations 参数在 New-SqlCertificateStoreColumnMasterKeySettings cmdlet 中的使用,这是两个示例中的工作流之间的唯一区别。
[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 -AllowEnclaveComputations
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 密钥保管库 预配已启用 enclave 的密钥
下面的端到端示例演示如何预配已启用 enclave 的密钥(将列主密钥存储在 Azure 密钥保管库 的密钥保管库中)。 该脚本基于不使用角色分隔的 Azure 密钥保管库(示例)中的示例。 请务必注意已启用 Enclave 的密钥与未启用 Enclave 的密钥相比,工作流之间有两个差异。
- 在下面的脚本中,New-SqlCertificateStoreColumnMasterKeySettings 使用
-AllowEnclaveComputations参数使新的列主密钥启用 enclave。 - 以下脚本使用命令小程序 Get-AzAccessToken 来获取密钥保管库的访问令牌。 这是必需的,因为命令 New-SqlAzureKeyVaultColumnMasterKeySettings 需要访问 Azure 密钥保管库 以对列主密钥的属性进行签名。
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 -AllowEnclaveComputations
$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
}