Alternar chaves do Always Encrypted usando o PowerShell

Aplica-se a:SQL ServerBanco de Dados SQL do AzureInstância Gerenciada de SQL do Azure

Este artigo fornece as etapas para girar chaves para Always Encrypted usando o módulo do SqlServer PowerShell. Para obter informações sobre como começar a usar o módulo do SqlServer PowerShell para Always Encrypted, consulte Configurar Always Encrypted usando o PowerShell.

Observação

A Microsoft recomenda usar o PowerShell 7 ou superior ao executar scripts do PowerShell para Always Encrypted. O PowerShell 7 fornece suporte multiplataforma aprimorado, melhor desempenho e compatibilidade com a versão mais recente do módulo SqlServer (v22+), que é necessária para muitos cenários do Always Encrypted.

Girar chaves Always Encrypted é o processo de substituir uma chave existente por uma nova. Talvez seja necessário girar uma chave se ela estiver comprometida ou estar em conformidade com as políticas da sua organização ou os regulamentos de conformidade que exigem rotação regular de chaves criptográficas.

Always Encrypted usa dois tipos de chaves, portanto, há dois fluxos de trabalho de rotação de chaves de alto nível; rotação de chaves mestras de coluna e rotação de chaves de criptografia de coluna.

  • Rotação de chave de criptografia de coluna - envolve descriptografar dados que estão criptografados com a chave atual e recriotografá-los usando a nova chave de criptografia de coluna. Como girar uma chave de criptografia de coluna requer acesso ao banco de dados e às chaves, a rotação de chaves de criptografia de coluna só pode ser executada sem a separação de funções.
  • Rotação de chave mestra de coluna - envolve a descriptografia de chaves de criptografia de coluna que são protegidas com a chave mestra de coluna atual, criptografando-as novamente usando a nova chave mestra de coluna e atualizando os metadados para os dois tipos de chaves. A rotação de chave mestra de coluna pode ser concluída com ou sem a separação de funções (usando o módulo do SqlServer PowerShell).

Rotação de chave mestra da coluna sem separação de funções

O método descrito nesta seção para girar uma chave mestra de coluna não dá suporte à separação de função entre um administrador de segurança e um DBA. Algumas das etapas a seguir combinam operações nas chaves físicas com operações em metadados de chave. Use esse fluxo de trabalho se sua organização usar o modelo DevOps ou quando o banco de dados estiver hospedado na nuvem e o objetivo principal for restringir os administradores de nuvem (mas não os DBAs locais) de acessar dados confidenciais. Não use esse método se potenciais adversários incluem DBAs ou se os DBAs não devem ter acesso a dados confidenciais.

Tarefa Artigo Acessa chaves de texto não criptografado/repositório de chaves Acessar banco de dados
Etapa 1. Crie uma nova chave mestra de coluna em um repositório de chaves.

Observação: O módulo do SqlServer PowerShell não dá suporte a essa etapa. Para realizar essa tarefa da linha de comando, você precisa usar ferramentas especificas para o seu repositório de chaves. Quando o Azure Key Vault é usado como repositório de chaves, não há suporte para a rotação de chaves gerenciadas pelo cliente multilocatário. Verifique se a nova chave gerenciada pelo cliente está no mesmo locatário que a existente.
Criar e armazenar chaves mestras de coluna para Always Encrypted Sim Não
Etapa 2. Inicie um ambiente do PowerShell e importe o módulo do SqlServer Importar o módulo do SqlServer Não Não
Etapa 3. Conecte-se ao servidor e banco de dados. Conectando-se a um banco de dados Não Sim
Etapa 4. Crie um objeto SqlColumnMasterKeySettings contendo informações sobre o local da sua nova chave mestra de coluna. SqlColumnMasterKeySettings é um objeto que existe na memória (no PowerShell). Para criá-lo, você precisa usar o cmdlet específico para o repositório de chaves. New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Não Não
Etapa 5. Crie os metadados da nova chave mestra de coluna no banco de dados. New-SqlColumnMasterKey

Observação: nos covers, esse cmdlet emite a CREATE COLUMN MASTER KEY instrução (Transact-SQL) para criar metadados de chave.
Não Sim
Etapa 6. Autentique-se no Azure, se a chave mestra de coluna atual ou nova estiver armazenada no cofre de chaves ou em um HSM gerenciado no Azure Key Vault Connect-AzAccount Sim Não
Etapa 7. Obtenha um token de acesso para o Azure Key Vault se sua chave mestra de coluna estiver armazenada no Azure Key Vault. Get-AzAccessToken Não Não
Etapa 8. Comece o processo de rotação criptografando cada uma das chaves de criptografia de coluna, que estão atualmente protegidas pela antiga chave mestra de coluna, usando a nova chave mestra de coluna. Após essa etapa, cada chave de criptografia de coluna afetada (associada à chave mestra de coluna antiga, que está sendo rotacionada) é criptografada com a chave mestra de coluna antiga e a nova, e possui dois valores criptografados nos metadados do banco de dados. Invoke-SqlColumnMasterKeyRotation Sim Sim
Etapa 9. Coordene com os administradores de todos os aplicativos que consultam colunas criptografadas no banco de dados (e são protegidos pela chave mestra de coluna antiga), para que eles possam garantir os aplicativos possam acessar a nova chave mestra de coluna. Criar e armazenar chaves mestras de coluna (Always Encrypted) Sim Não
Etapa 10. Conclua a rotação

Observação: antes de executar essa etapa, verifique se todos os aplicativos que consultam colunas criptografadas que são protegidas com a chave mestra de coluna antiga foram configurados para usar a nova chave mestra de coluna. Se você executar essa etapa prematuramente, alguns desses aplicativos não poderão descriptografar os dados. Conclua a rotação removendo os valores criptografados dos bancos de dados que foram criados com a chave mestra de coluna antiga. Isso remove a associação entre a chave mestra de coluna antiga e as chaves de criptografia de coluna que ela protege.
Complete-SqlColumnMasterKeyRotation Não Sim
Etapa 11. Remova os metadados de chave mestra de coluna antiga. Remove-SqlColumnMasterKey Não Sim

Observação

É altamente recomendável não excluir a chave mestra de coluna antiga permanentemente após a rotação. Em vez disso, você deve manter a chave mestra de coluna antiga em seu repositório de chaves atual ou arquivá-la em outro local seguro. Se você restaurar seu banco de dados de um arquivo de backup para um ponto anterior à configuração da nova chave mestra de coluna, precisará da chave antiga para acessar os dados.

Girando uma chave mestra de coluna sem separação de funções (exemplo de Certificado do Windows)

O script abaixo é um exemplo de ponta a ponta que substitui uma chave mestra de coluna existente (CMK1) por uma nova chave mestra de coluna (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.'

Rotação de chave mestra da coluna com separação de funções

O fluxo de trabalho de rotação de chave mestra de coluna descrito nesta seção garante a separação entre um Administrador de Segurança e um DBA.

Importante

Antes de executar as etapas em que Acessa chaves de texto não criptografado/repositório de chaves=Sim na tabela a seguir (etapas que acessam chaves de texto não criptografado ou o repositório de chaves), verifique se o ambiente do PowerShell é executado em um computador seguro diferente daquele que hospeda o banco de dados. Para obter mais informações, consulte Considerações de Segurança para o Gerenciamento de Chaves.

Parte 1: DBA

Um DBA recupera metadados da chave mestra de coluna a ser girada e das chaves de criptografia do coluna afetadas associadas à chave mestra de coluna atual. O DBA compartilha todas essas informações com um Administrador de Segurança.

Tarefa Artigo Acessa chaves de texto não criptografado/repositório de chaves Acessar banco de dados
Etapa 1. Inicie um ambiente do PowerShell e importe o módulo do SqlServer. Importar o módulo do SqlServer Não Nenhum
Etapa 2. Conecte-se ao seu servidor e a um banco de dados. Conectar a um banco de dados Não Sim
Etapa 3. Recupere os metadados da chave mestra de coluna antiga. Get-SqlColumnMasterKey Não Sim
Etapa 4. Recupere os metadados das chaves de criptografia de coluna, protegidas pela antiga chave mestra de coluna, incluindo seus valores criptografados. Get-SqlColumnEncryptionKey Não Sim
Etapa 5. Compartilhe o local da chave mestra de coluna (o nome do provedor e o caminho principal da chave mestra de coluna) e dos valores criptografados das chaves de criptografia de coluna correspondentes, protegidas pela chave mestra de coluna antiga. Confira os exemplos abaixo. Não Não

Parte 2: Administrador de Segurança

O Administrador de Segurança gera uma nova chave mestra de coluna, criptografa novamente as chaves de criptografia de coluna afetadas com a nova chave mestra de coluna e compartilha as informações da nova chave mestra de coluna, bem como o conjunto de novos valores criptografados para as chaves de criptografia de coluna afetadas, com o DBA.

Tarefa Artigo Acessar chaves de texto não criptografado/repositório de chaves Acessar banco de dados
Etapa 1. Obtenha com o DBA o local da chave mestra de coluna antiga e dos valores criptografados das chaves de criptografia de coluna correspondentes, protegidas pela chave mestra de coluna antiga. Não aplicável
Confira os exemplos abaixo.
Não Não
Etapa 2. Crie uma nova chave mestra de coluna em um repositório de chaves.

Observação: O módulo do SqlServer não dá suporte a essa etapa. Para realizar essa tarefa da linha de comando, você precisa usar ferramentas especificas para o tipo do seu repositório de chaves. Quando o Azure Key Vault é usado como repositório de chaves, não há suporte para a rotação de chaves gerenciadas pelo cliente multilocatário. Verifique se a nova chave gerenciada pelo cliente está no mesmo locatário que a existente.
Criar e armazenar chaves mestras de coluna para Always Encrypted Sim Não
Etapa 3. Inicie um ambiente do PowerShell e importe o módulo do SqlServer. Importar o módulo do SqlServer Não Não
Etapa 4. Crie um objeto SqlColumnMasterKeySettings contendo informações sobre o local da sua chave mestra de coluna antiga . SqlColumnMasterKeySettings é um objeto que existe na memória (no PowerShell). New-SqlColumnMasterKeySettings Não Não
Etapa 5. Crie um objeto SqlColumnMasterKeySettings contendo informações sobre o local da sua nova chave mestra de coluna. SqlColumnMasterKeySettings é um objeto que existe na memória (no PowerShell). Para criá-lo, você precisa usar o cmdlet específico para o repositório de chaves. New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Não Não
Etapa 6. Autentique no Azure, se a sua chave mestra de coluna antiga (atual) ou a nova estiver armazenada em um cofre de chaves ou em um HSM gerenciado no Azure Key Vault. Connect-AzAccount Sim Não
Etapa 7. Obtenha um token de acesso para o Azure Key Vault se sua chave mestra de coluna estiver armazenada no Azure Key Vault. Get-AzAccessToken Não Não
Etapa 8. Criptografe novamente cada valor das chaves de criptografia de coluna, que atualmente está protegida pela chave mestra de coluna antiga, usando a nova chave mestra de coluna. New-SqlColumnEncryptionKeyEncryptedValue

Observação: ao chamar esse cmdlet, passe os objetos SqlColumnMasterKeySettings para ambas as chaves mestras de coluna antiga e nova, juntamente com um valor da chave de criptografia de coluna a ser recriptografada.
Sim Não
Etapa 9. Compartilhe o local da nova chave mestra de coluna (o nome do provedor e o caminho principal da chave mestra de coluna) e do conjunto de novos valores criptografados das chaves de criptografia de coluna, com seu DBA. Confira os exemplos abaixo. Não Não

Observação

É altamente recomendável não excluir a chave mestra de coluna antiga permanentemente após a rotação. Em vez disso, você deve manter a chave mestra de coluna antiga em seu repositório de chaves atual ou arquivá-la em outro local seguro. Se você restaurar seu banco de dados de um arquivo de backup para um ponto anterior à configuração da nova chave mestra de coluna, precisará da chave antiga para acessar os dados.

Parte 3: DBA

O DBA cria metadados para a nova chave mestra de coluna e atualiza os metadados das chaves de criptografia de coluna afetadas para adicionar o novo conjunto de valores criptografados. Nesta etapa, o DBA também coordena com os administradores dos aplicativos que consultam as colunas de criptografia, os quais garantem que os aplicativos possam acessar a nova chave mestra de coluna. Depois que todos os aplicativos são configurados para usar a nova chave mestra de coluna, o DBA remove o conjunto antigo de valores criptografados e os metadados da chave mestra de coluna antiga.

Tarefa Artigo Acessar chaves de texto não criptografado/repositório de chaves Acessar banco de dados
Etapa 1. Obtenha do Administrador de Segurança o local da nova chave mestra de coluna e o novo conjunto de valores criptografados das chaves de criptografia de coluna correspondentes, protegidas pela chave mestra de coluna antiga. Confira os exemplos abaixo. Não Não
Etapa 2. Inicie um ambiente do PowerShell e importe o módulo do SqlServer. Importar o módulo do SqlServer Não Não
Etapa 3. Conecte-se ao seu servidor e a um banco de dados. Conectando-se a um banco de dados Não Sim
Etapa 4. Crie um objeto SqlColumnMasterKeySettings contendo informações sobre o local da sua nova chave mestra de coluna. SqlColumnMasterKeySettings é um objeto que existe na memória (no PowerShell). New-SqlColumnMasterKeySettings Não Não
Etapa 5. Crie os metadados da nova chave mestra de coluna no banco de dados. New-SqlColumnMasterKey

Observação: Sob as capas, esse cmdlet emite a CREATE COLUMN MASTER KEY instrução (Transact-SQL) para criar metadados de chave.
Não Sim
Etapa 6. Recupere os metadados das chaves de criptografia de coluna, protegidas pela antiga chave mestra de coluna. Get-SqlColumnEncryptionKey Não Sim
Etapa 7. Adicione um novo valor criptografado (produzido usando a nova chave mestra de coluna) aos metadados para cada chave de criptografia de coluna afetada. Add-SqlColumnEncryptionKeyValue Não Sim
Etapa 8. Coordene com os administradores de todos os aplicativos que consultam colunas criptografadas no banco de dados (e são protegidos pela chave mestra de coluna antiga), para que eles possam garantir os aplicativos possam acessar a nova chave mestra de coluna. Criando e armazenando chaves mestras de coluna (Always Encrypted) Não Não
Etapa 9. Conclua a rotação removendo os valores criptografados associados à chave mestra de coluna antiga do banco de dados.

Observação: antes de executar essa etapa, verifique se todos os aplicativos que consultam colunas criptografadas que são protegidas com a chave mestra de coluna antiga foram configurados para usar a nova chave mestra de coluna. Se você executar essa etapa prematuramente, alguns desses aplicativos não poderão descriptografar os dados.

Essa etapa remove uma associação entre a chave mestra de coluna antiga e as chaves de criptografia de coluna que ela protege.
Complete-SqlColumnMasterKeyRotation

Como alternativa, você pode usar Remove-SqlColumnEncryptionKeyValue
Não Sim
Etapa 10. Remover os metadados da antiga chave mestra de coluna do banco de dados Remove-SqlColumnMasterKey Não Sim

Girar uma chave mestra de coluna com separação de funções (exemplo de Certificado do Windows)

O script abaixo é um exemplo de ponta a ponta para gerar uma nova chave mestra de coluna que é o certificado no repositório de Certificados do Windows, girando uma chave mestra de coluna existente (atual) para substituí-la pela nova chave mestra de coluna. O script supõe que o banco de dados de destino contém a chave mestra de coluna, chamada CMK1 (que será girada), o qual criptografa algumas chaves de criptografia de coluna.

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

Parte 2: Administrador de Segurança

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

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

Girando uma chave de criptografia de coluna

Rotacionar uma chave de criptografia de coluna envolve primeiro descriptografar os dados em todas as colunas que foram criptografados com a chave a ser rotacionada, e então criptografar novamente esses dados usando a nova chave de criptografia de coluna. Esse fluxo de trabalho de rotação requer acesso às chaves e ao banco de dados, portanto, não pode ser executado com a separação de funções. Rotacionar uma chave de criptografia de coluna pode levar muito tempo, especialmente se as tabelas que contêm colunas criptografadas com a chave em rotação forem grandes. Portanto, sua organização precisa planejar uma rotação de chave de criptografia de coluna com cuidado.

Você pode girar uma chave de criptografia de coluna usando uma abordagem offline ou online. O primeiro método é provavelmente mais rápido, mas seus aplicativos não conseguem gravar nas tabelas afetadas. A segunda abordagem provavelmente levará mais tempo, mas você pode limitar o intervalo de tempo durante o qual as tabelas afetadas não estarão disponíveis para os aplicativos. Para mais informações, confira Configurar criptografia de coluna usando Always Encrypted com o PowerShell e Set-SqlColumnEncryption.

Tarefa Artigo Acessa chaves de texto não criptografado/repositório de chaves Acessar banco de dados
Etapa 1. Inicie um ambiente do PowerShell e importe o módulo do SqlServer. Importar o módulo do SqlServer Não Não
Etapa 2. Conecte-se ao seu servidor e a um banco de dados. Conectando-se a um banco de dados Não Sim
Etapa 3. Autentique no Azure se a chave mestra de coluna (que protege a chave de criptografia de coluna a ser girada) estiver armazenada no cofre de chaves ou em um HSM gerenciado do Azure Key Vault. Connect-AzAccount Sim Não
Etapa 4. Obtenha um token de acesso para o Azure Key Vault se sua chave mestra de coluna estiver armazenada no Azure Key Vault. Get-AzAccessToken Não Não
Etapa 5. Gere uma nova chave de criptografia de coluna, criptografe-a com a chave mestra de coluna e crie metadados de chave de criptografia de coluna no banco de dados. New-SqlColumnEncryptionKey

Observação: use uma variação do cmdlet que gera e criptografa internamente uma chave de criptografia de coluna.
Sob as capas, esse cmdlet emite a CREATE COLUMN ENCRYPTION KEY instrução (Transact-SQL) para criar os metadados de chave.
Sim Sim
Etapa 6. Localize todas as colunas criptografadas com a chave de criptografia de coluna antiga. Guia de Programação do SQL Server Management Objects (SMO) Não Sim
Etapa 7. Crie um objeto SqlColumnEncryptionSettings para cada coluna afetada. SqlColumnEncryptionSettings é um objeto que existe na memória (no PowerShell). Especifica o esquema de criptografia de destino para uma coluna. Nesse caso, o objeto deve especificar que a coluna afetada deve ser criptografada usando a nova chave de criptografia de coluna. New-SqlColumnEncryptionSettings Não Não
Etapa 8. Criptografe novamente as colunas identificadas na etapa 5 usando a nova chave de criptografia de coluna. Set-SqlColumnEncryption

Observação: esta etapa pode levar muito tempo. Seus aplicativos não poderão acessar as tabelas durante a operação inteira ou de parte dela, dependendo da abordagem (online versus offline) selecionada.
Sim Sim
Etapa 9. Remova os metadados da chave de criptografia de coluna antiga. Remove-SqlColumnEncryptionKey Não Sim

Exemplo: girando uma chave de criptografia de coluna

O script abaixo demonstra a rotação de uma chave de criptografia de coluna. O script supõe que o banco de dados de destino contém algumas colunas criptografadas com uma chave de criptografia de coluna chamada CEK1 (que será girada), que é protegida usando uma chave mestra de coluna chamada CMK1 (a chave mestra de coluna não está armazenada no 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'

Próximas etapas

Confira também