Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Se aplica a:SQL Server
Azure SQL Database
Azure SQL Managed Instance
En este artículo se proporcionan los pasos necesarios para rotar claves de Always Encrypted con el módulo SqlServer PowerShell. Para obtener información sobre el uso del módulo SqlServer PowerShell para Always Encrypted, vea Configure Always Encrypted using PowerShell (Configurar Always Encrypted con PowerShell).
Nota:
Microsoft recomienda usar PowerShell 7 o posterior al ejecutar scripts de PowerShell de Always Encrypted. PowerShell 7 proporciona compatibilidad multiplataforma mejorada, un mejor rendimiento y la compatibilidad más reciente con el módulo SqlServer (v22+), que es necesario para muchos escenarios de Always Encrypted.
La rotación de claves Always Encrypted es el proceso de reemplazar una clave existente por una nueva. Es posible que tenga que rotar una clave si está en peligro o cumplir las directivas de su organización o las regulaciones de cumplimiento que exigen la rotación normal de claves criptográficas.
Always Encrypted emplea dos tipos de claves, por lo que hay dos flujos de trabajo de rotación de claves de alto nivel: rotación de claves maestras de columna y rotación de claves de cifrado de columna.
- Rotación de claves de cifrado de columna : implica descifrar los datos cifrados con la clave actual y volver a cifrarlos con la nueva clave de cifrado de columna. Como la rotación de una clave de cifrado de columna exige el acceso a las claves y a la base de datos, la rotación de claves de cifrado de columna solo se puede realizar sin separación de roles.
- Rotación de claves maestras de columna : implica descifrar las claves de cifrado de columna que están protegidas con la clave maestra de columna actual, volver a cifrarlas con la nueva clave maestra de columna y actualizar los metadatos de ambos tipos de claves. La rotación de claves maestras de columna se puede realizar con o sin separación de roles (cuando se usa el módulo SqlServer PowerShell).
Rotación de claves maestras de columna sin separación de roles
El método descrito en esta sección para rotar una clave maestra de columna no admite la separación de roles entre un administrador de seguridad y un DBA. Algunos de los pasos siguientes combinan operaciones en las claves físicas con operaciones en metadatos de clave. Use este flujo de trabajo si la organización usa el modelo de DevOps o cuando la base de datos está hospedada en la nube y el objetivo principal es restringir a los administradores de la nube (pero no a los DBA locales) para acceder a datos confidenciales. No use este método si los posibles adversarios incluyen DBA o si los DBA no deben tener acceso a datos confidenciales.
| Tarea | Artículo | Accede a claves de texto no cifrado o a almacén de claves | Accede a base de datos |
|---|---|---|---|
| Paso 1. Cree una nueva clave maestra de columna en un almacén de claves. Nota: El módulo SqlServer de PowerShell no admite este paso. Para realizar esta tarea desde la línea de comandos, debe usar herramientas específicas del almacén de claves. Al usar Azure Key Vault como almacén de claves, no se admite la rotación de claves administradas por el cliente multiinquilino. Asegúrate de que la nueva clave administrada por el cliente esté en la misma entidad que la existente. |
Creación y almacenamiento de claves maestras de columna para Always Encrypted | Sí | No |
| Paso 2. Inicie un entorno de PowerShell e importe el módulo SqlServer. | Importar el módulo SqlServer | No | No |
| Paso 3. Conecte con el servidor y la base de datos. | Conexión a una base de datos | No | Sí |
| Paso 4. Cree un objeto SqlColumnMasterKeySettings que contenga información sobre la ubicación de la nueva clave maestra de columna. SqlColumnMasterKeySettings es un objeto que existe en memoria (en PowerShell). Para crearlo, debe usar el cmdlet específico del almacén de claves. |
New-SqlAzureKeyVaultColumnMasterKeySettings New-SqlCertificateStoreColumnMasterKeySettings New-SqlCngColumnMasterKeySettings New-SqlCspColumnMasterKeySettings |
No | No |
| Paso 5. Cree los metadatos sobre la nueva clave maestra de columna en la base de datos. |
New-SqlColumnMasterKey Nota: En segundo plano, este cmdlet emite la CREATE COLUMN MASTER KEY instrucción (Transact-SQL) para crear metadatos de clave. |
No | Sí |
| Paso 6. Autentíquese en Azure si la clave maestra de columna actual o la nueva se almacena en un almacén de claves o un HSM administrado en Azure Key Vault. | Connect-AzAccount | Sí | No |
| Paso 7. Obtenga un token de acceso para las instancias de Azure Key Vault, si la clave maestra de columna está almacenada en Azure Key Vault. | Get-AzAccessToken | No | No |
| Paso 8. Comience la rotación cifrando con la nueva clave maestra de columna cada una de las claves de cifrado de columna, que actualmente están protegidas con la antigua clave maestra de columna. Después de este paso, cada clave de cifrado de columna afectada (asociada a la antigua clave maestra de columna y rotada) se cifra con la antigua y la nueva clave maestra de columna y tiene dos valores cifrados en los metadatos de la base de datos. | Invoke-SqlColumnMasterKeyRotation | Sí | Sí |
| Paso 9. Coordínese con los administradores de todas las aplicaciones que consultan columnas cifradas de la base de datos (y protegidas con la clave maestra de columna anterior) para que puedan garantizar el acceso de las aplicaciones a la nueva clave maestra de columna. | Create and Store Column Master Keys (Always Encrypted) (Crear y almacenar claves maestras de columna (Always Encrypted)) | Sí | No |
| Paso 10. Finalice la rotación. Nota: Antes de ejecutar este paso, asegúrese de que todas las aplicaciones que consultan columnas cifradas protegidas con la clave maestra de columna anterior se hayan configurado para usar la nueva clave maestra de columna. Si realiza este paso antes de tiempo, es posible que algunas de esas aplicaciones no puedan descifrar los datos. Finalice la rotación mediante la eliminación de los valores cifrados de la base de datos que se crearon con la clave maestra de columna anterior. Esto elimina la asociación entre la antigua clave maestra de columna y las claves de cifrado de columna que protege. |
Complete-SqlColumnMasterKeyRotation | No | Sí |
| Paso 11. Elimine los metadatos de la antigua clave maestra de columna. | Remove-SqlColumnMasterKey | No | Sí |
Nota:
Es muy recomendable no eliminar permanentemente la antigua clave maestra de columna después de la rotación. Debería conservarla en su almacén de claves actual o archivarla en otra ubicación segura. Si restaura la base de datos desde un archivo de copia de seguridad a un punto en el tiempo anterior a la configuración de la nueva clave maestra de columna, necesitará la clave antigua para acceder a los datos.
Rotación de una clave maestra de columna sin separación de roles (ejemplo de certificado de Windows)
El siguiente script es un ejemplo completo que reemplaza una clave maestra de columna existente (CMK1) por una nueva clave maestra de columna (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.'
Rotación de claves maestras de columna con separación de roles
El flujo de trabajo de rotación de claves maestras de columna que se describe en esta sección garantiza la separación entre un Administrador de seguridad y un DBA.
Importante
Antes de ejecutar cualquiera de los pasos donde Accede a claves de texto no cifrado o a almacén de claves=Sí en la tabla siguiente (pasos que acceden a claves de texto no cifrado o a almacén de claves), asegúrese de que el entorno de PowerShell se ejecuta en un equipo seguro distinto al que hospeda la base de datos. Para obtener más información, consulte Security Considerations for Key Management (Consideraciones de seguridad para la administración de claves).
Parte 1: DBA
Un DBA recupera metadatos sobre la clave maestra de columna que se va a rotar y sobre las claves de cifrado de columna afectadas asociadas a la clave maestra de columna actual. El DBA comparte toda esta información con un Administrador de seguridad.
| Tarea | Artículo | Accede a claves de texto no cifrado o a almacén de claves | Accede a base de datos |
|---|---|---|---|
| Paso 1. Inicie un entorno de PowerShell e importe el módulo SqlServer. | Importar el módulo SqlServer | No | Ninguno |
| Paso 2. Conecte con el servidor y una base de datos. | Conexión a una base de datos | No | Sí |
| Paso 3. Recupere los metadatos sobre la clave maestra de columna antigua. | Get-SqlColumnMasterKey | No | Sí |
| Paso 4. Recupere los metadatos sobre las claves de cifrado de columna protegidas por la antigua clave maestra de columna, incluidos sus valores cifrados. | Get-SqlColumnEncryptionKey | No | Sí |
| Paso 5. Comparta la ubicación de la clave maestra de columna (el nombre del proveedor y una ruta de acceso de la clave maestra de columna) y los valores cifrados de las claves de cifrado de columna correspondientes protegidos con la clave maestra de columna anterior. | Consulte los ejemplos más abajo. | No | No |
Parte 2: Administrador de seguridad
El Administrador de seguridad genera una nueva clave maestra de columna, vuelve a cifrar las claves de cifrado de columna afectadas con la nueva clave maestra de columna y comparte la información sobre la nueva clave maestra de columna, así como el conjunto de nuevos valores cifrados de las claves de cifrado de columna afectadas, con el DBA.
| Tarea | Artículo | Acceso a claves de texto no cifrado o a almacén de claves | Accede a base de datos |
|---|---|---|---|
| Paso 1. Obtenga de su DBA la ubicación de la clave maestra de columna antigua y los valores cifrados de las claves de cifrado de columna correspondientes, que están protegidos con la clave maestra de columna antigua. | No disponible Consulte los ejemplos más abajo. |
No | No |
| Paso 2. Cree una nueva clave maestra de columna en un almacén de claves. Nota: El módulo SqlServer no admite este paso. Para realizar esta tarea desde una línea de comandos, debe usar herramientas específicas del tipo del almacén de claves. Al usar Azure Key Vault como almacén de claves, no se admite la rotación de claves administradas por el cliente multiinquilino. Asegúrate de que la nueva clave administrada por el cliente esté en la misma entidad que la existente. |
Creación y almacenamiento de claves maestras de columna para Always Encrypted | Sí | No |
| Paso 3. Inicie un entorno de PowerShell e importe el módulo SqlServer. | Importar el módulo SqlServer | No | No |
| Paso 4. Cree un objeto SqlColumnMasterKeySettings que contenga información sobre la ubicación de la antigua clave maestra de columna. SqlColumnMasterKeySettings es un objeto que existe en memoria (en PowerShell). | New-SqlColumnMasterKeySettings | No | No |
| Paso 5. Cree un objeto SqlColumnMasterKeySettings que contenga información sobre la ubicación de la nueva clave maestra de columna. SqlColumnMasterKeySettings es un objeto que existe en memoria (en PowerShell). Para crearlo, debe usar el cmdlet específico del almacén de claves. |
New-SqlAzureKeyVaultColumnMasterKeySettings New-SqlCertificateStoreColumnMasterKeySettings New-SqlCngColumnMasterKeySettings New-SqlCspColumnMasterKeySettings |
No | No |
| Paso 6. Autentíquese en Azure si la clave maestra de columna antigua (actual) o la nueva se almacena en un almacén de claves o un HSM administrado en Azure Key Vault. | Connect-AzAccount | Sí | No |
| Paso 7. Obtenga un token de acceso para las instancias de Azure Key Vault, si la clave maestra de columna está almacenada en Azure Key Vault. | Get-AzAccessToken | No | No |
| Paso 8. Vuelva a re-cifrar cada valor de la clave de cifrado de columna, que actualmente está protegida con la antigua clave maestra de columna, utilizando la nueva clave maestra de columna. |
New-SqlColumnEncryptionKeyEncryptedValue Nota: Al llamar a este cmdlet, pase los objetos SqlColumnMasterKeySettings de la antigua y la nueva clave maestra de columna, junto con un valor de la clave de cifrado de columna, para que se vuelvan a cifrar. |
Sí | No |
| Paso 9. Comparta la ubicación de la nueva clave maestra de columna (el nombre del proveedor y la ruta de acceso de la clave maestra de columna) y el conjunto de valores cifrados nuevos de las claves de cifrado de columna con el DBA. | Consulte los ejemplos más abajo. | No | No |
Nota:
Es muy recomendable no eliminar permanentemente la antigua clave maestra de columna después de la rotación. Debería conservarla en su almacén de claves actual o archivarla en otra ubicación segura. Si restaura la base de datos desde un archivo de copia de seguridad a un punto en el tiempo anterior a la configuración de la nueva clave maestra de columna, necesitará la clave antigua para acceder a los datos.
Parte 3: DBA
El DBA crea metadatos para la nueva clave maestra de columna y actualiza los metadatos de las claves de cifrado de columna afectadas para agregar el nuevo conjunto de valores cifrados. En este paso, el DBA también se coordina con los administradores de las aplicaciones que consultan las columnas de cifrado para asegurarse de que la aplicación pueda acceder a la nueva clave maestra de columna. Una vez que todas las aplicaciones están configuradas para usar la nueva clave maestra de columna, el DBA quita el conjunto anterior de valores cifrados y los metadatos antiguos de claves maestras de columna.
| Tarea | Artículo | Acceso a claves de texto no cifrado o a almacén de claves | Accede a base de datos |
|---|---|---|---|
| Paso 1. Obtenga la ubicación de la nueva clave maestra de columna y el nuevo conjunto de valores cifrados de las claves de cifrado de columna correspondientes protegidos con la clave maestra de columna antigua del Administrador de seguridad. | Consulte los ejemplos más abajo. | No | No |
| Paso 2. Inicie un entorno de PowerShell e importe el módulo SqlServer. | Importar el módulo SqlServer | No | No |
| Paso 3. Conecte con el servidor y una base de datos. | Conexión a una base de datos | No | Sí |
| Paso 4. Cree un objeto SqlColumnMasterKeySettings que contenga información sobre la ubicación de la nueva clave maestra de columna. SqlColumnMasterKeySettings es un objeto que existe en memoria (en PowerShell). | New-SqlColumnMasterKeySettings | No | No |
| Paso 5. Cree los metadatos sobre la nueva clave maestra de columna en la base de datos. |
New-SqlColumnMasterKey Nota: En segundo plano, este cmdlet emite la CREATE COLUMN MASTER KEY instrucción (Transact-SQL) para crear metadatos de clave. |
No | Sí |
| Paso 6. Recupere los metadatos sobre las claves de cifrado de columna protegidas por la antigua clave maestra de columna. | Get-SqlColumnEncryptionKey | No | Sí |
| Paso 7. Agregue un nuevo valor cifrado (generado con la nueva clave maestra de columna) a los metadatos de cada clave de cifrado de columna afectada. | Add-SqlColumnEncryptionKeyValue | No | Sí |
| Paso 8. Coordínese con los administradores de todas las aplicaciones que consultan columnas cifradas de la base de datos (y protegidas con la clave maestra de columna anterior) para que puedan garantizar el acceso de las aplicaciones a la nueva clave maestra de columna. | Creación y almacenamiento de claves maestras de columna (Always Encrypted) | No | No |
| Paso 9. Finalice la rotación mediante la eliminación de los valores cifrados asociados a la clave maestra de columna anterior de la base de datos. Nota: Antes de ejecutar este paso, asegúrese de que todas las aplicaciones que consultan columnas cifradas protegidas con la clave maestra de columna anterior se hayan configurado para usar la nueva clave maestra de columna. Si realiza este paso antes de tiempo, es posible que algunas de esas aplicaciones no puedan descifrar los datos. Este paso quita una asociación entre la antigua clave maestra de columna y las claves de cifrado de columna que protege. |
Complete-SqlColumnMasterKeyRotation También puede usar Remove-SqlColumnEncryptionKeyValue |
No | Sí |
| Paso 10. Quite de la base de datos los metadatos antiguos de la clave maestra de columna. | Remove-SqlColumnMasterKey | No | Sí |
Rotación de una clave maestra de columna con separación de roles (ejemplo de certificado de Windows)
El siguiente script es un ejemplo completo para generar una nueva clave maestra de columna que está certificada en el almacén de certificados de Windows y rotar una clave maestra de columna actualmente existente para reemplazarla por la nueva clave maestra de columna. El script da por hecho que la base de datos de destino contiene la clave maestra de columnas, denominada CMK1 (que se va a rotar), y que cifra algunas claves de cifrado de columnas.
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 seguridad
[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'
Rotación de una clave de cifrado de columna
La rotación de una clave de cifrado de columna implica descifrar los datos de todas las columnas cifrados con la clave que se va a rotar y volver a cifrarlos con la nueva clave de cifrado de columna. Este flujo de trabajo de rotación exige acceso a las claves y a la base de datos y, por tanto, no se puede realizar con separación de roles. La rotación de una clave de cifrado de columna puede tardar mucho tiempo si las tablas que contienen columnas cifradas con la clave que se va a rotar son grandes. Por lo tanto, la organización tiene que planear cuidadosamente cualquier rotación de claves de cifrado de columna.
Puede girar una clave de cifrado de columna con un enfoque sin conexión o un enfoque en línea. El primer método probablemente sea más rápido, pero las aplicaciones no pueden escribir en las tablas afectadas. El último enfoque probablemente demore más, pero puede limitar el intervalo de tiempo durante el cual las tablas afectadas no estarán disponibles para las aplicaciones. Para más información, consulte Configuración del cifrado de columna mediante Always Encrypted con PowerShell y Set-SqlColumnEncryption.
| Tarea | Artículo | Accede a claves de texto no cifrado o a almacén de claves | Accede a base de datos |
|---|---|---|---|
| Paso 1. Inicie un entorno de PowerShell e importe el módulo SqlServer. | Importar el módulo SqlServer | No | No |
| Paso 2. Conecte con el servidor y una base de datos. | Conexión a una base de datos | No | Sí |
| Paso 3. Autentíquese en Azure si su clave maestra de columna, que protege la clave de cifrado de columna que se va a rotar, está almacenada en un almacén de claves o en un HSM gestionado dentro de Azure Key Vault. | Connect-AzAccount | Sí | No |
| Paso 4. Obtenga un token de acceso para las instancias de Azure Key Vault, si la clave maestra de columna está almacenada en Azure Key Vault. | Get-AzAccessToken | No | No |
| Paso 5. Genere una nueva clave de cifrado de columna, cífrela con la clave maestra de columna y cree los metadatos de clave de cifrado de columna en la base de datos. |
New-SqlColumnEncryptionKey Nota: Use una variación del cmdlet que genera internamente y cifra una clave de cifrado de columna. En segundo plano, este cmdlet emite la CREATE COLUMN ENCRYPTION KEY instrucción (Transact-SQL) para crear los metadatos de clave. |
Sí | Sí |
| Paso 6. Busque todas las columnas cifradas con la clave de cifrado de columna anterior. | Guía de programación para objetos de administración de SQL Server (SMO) | No | Sí |
| Paso 7. Cree un objeto SqlColumnEncryptionSettings para cada columna afectada. SqlColumnEncryptionSettings es un objeto que existe en memoria (en PowerShell). Especifica el esquema de cifrado de destino de una columna. En este caso, el objeto debe especificar que la columna afectada debe cifrarse con la nueva clave de cifrado de columna. | ConfiguraciónNuevaDeCifradoDeColumnasSql | No | No |
| Paso 8. Vuelva a cifrar las columnas identificadas en el paso 5 con la nueva clave de cifrado de columna. |
Set-SqlColumnEncryption Nota: Este paso puede llevar mucho tiempo. Las aplicaciones no podrán tener acceso a las tablas durante toda la operación o parte de esta, dependiendo del enfoque (en línea o sin conexión) que seleccione. |
Sí | Sí |
| Paso 9. Quite los metadatos de la clave de cifrado de columna anterior. | Eliminar-SqlColumnEncryptionKey | No | Sí |
Ejemplo: rotación de una clave de cifrado de columna
El siguiente script muestra la rotación de una clave de cifrado de columna. El script da por hecho que la base de datos de destino contiene algunas columnas cifradas con una clave de cifrado de columna denominada CEK1 (que se va a rotar), que está protegida con una clave maestra de columna denominada CMK1 (la clave maestra de columna no se almacena en 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'
Pasos siguientes
- Consulta de columnas mediante Always Encrypted con SQL Server Management Studio
- Desarrollo de aplicaciones con Always Encrypted
Consulte también
- Always Encrypted
- Información general sobre la administración de claves de Always Encrypted
- Configurar Always Encrypted con PowerShell
- Rotación de claves de Always Encrypted mediante SQL Server Management Studio
- CREATE COLUMN MASTER KEY (Transact-SQL)
- DROP COLUMN MASTER KEY (Transact-SQL)
- CREATE COLUMN ENCRYPTION KEY (Transact-SQL)
- ALTER COLUMN ENCRYPTION KEY (Transact-SQL)
- DROP COLUMN ENCRYPTION KEY (Transact-SQL)
- sys.column_master_keys (Transact-SQL)
- sys.column_encryption_keys (Transact-SQL)