预配已启用 enclave 的密钥

适用于: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 时,才会显示“允许 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-SqlCertificateStoreColumnMasterKeySettingsNew-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
}