Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Применимо к:SQL Server
База данных
SQL AzureУправляемый экземпляр SQL Azure
В этой статье описаны действия по смене ключей постоянного шифрования с помощью модуля SqlServer PowerShell. Сведения о том, как начать использовать модуль SqlServer PowerShell для Always Encrypted, см. в разделе "Настройка Always Encrypted" с помощью PowerShell.
Примечание.
Microsoft рекомендует использовать PowerShell 7 или более поздней версии при выполнении скриптов PowerShell always Encrypted. PowerShell 7 обеспечивает улучшенную кроссплатформенную поддержку, более высокую производительность и последнюю совместимость с модулем SqlServer (версии 22+), который необходим для многих сценариев Always Encrypted.
Ротация ключей Always Encrypted — это процесс замены существующего ключа новым. Возможно, потребуется сменить ключ, если он скомпрометирован или соответствовать политикам вашей организации или нормативным требованиям, которые требуют регулярного смены криптографических ключей.
Always Encrypted использует два типа ключей, поэтому существуют два высокоуровневых рабочих процесса по смене ключей: смена мастер-ключей столбцов и смена ключей шифрования столбцов.
- Смена ключа шифрования столбцов — включает расшифровку данных, зашифрованных с помощью текущего ключа, и повторное шифрование данных с помощью нового ключа шифрования столбца. Поскольку для смены ключа шифрования столбца требуется доступ к ключам и базе данных, смена ключей шифрования столбцов осуществляется только без разделения ролей.
- Поворот главного ключа столбца — включает расшифровку ключей шифрования столбцов, защищенных с помощью текущего главного ключа столбца, повторного шифрования их с помощью нового главного ключа столбца и обновления метаданных для обоих типов ключей. Смена главного ключа столбца может быть выполнена как с разделением ролей, так и без него, при использовании модуля SqlServer PowerShell.
Смена главного ключа столбца без разделения ролей
Метод, описанный в этом разделе для смены главного ключа столбца, не поддерживает разделение ролей между администратором безопасности и DBA. Некоторые из следующих шагов объединяют операции с физическими ключами с операциями с метаданными ключей. Используйте этот рабочий процесс, если ваша организация использует модель DevOps или когда база данных размещена в облаке, а основная цель — ограничить доступ к конфиденциальным данным администраторам облачных служб (но не локальным базам данных). Не используйте этот метод, если среди потенциальных злоумышленников могут быть администраторы баз данных или если у администраторов баз данных не должно быть доступа к конфиденциальным данным.
| Задача | Статья | Доступ к ключам с открытым текстом или хранилищу ключей | Доступ к базе данных |
|---|---|---|---|
| Шаг 1. Создайте новый главный ключ столбца в хранилище ключей. Заметка: Модуль SqlServer PowerShell не поддерживает этот шаг. Для выполнения этой задачи из командной строки используйте средства, поддерживаемые выбранным хранилищем ключей. При использовании Azure Key Vault в качестве хранилища ключей смена клиентом управляемых ключей для многопользовательских клиентов не поддерживается. Убедитесь, что новый ключ, управляемый клиентом, находится в том же арендаторе, что и существующий. |
Создание и хранение главных ключей столбцов для Always Encrypted | Да | Нет |
| Шаг 2. Запуск среды PowerShell и импорт модуля SqlServer. | Импорт модуля SqlServer | Нет | Нет |
| Шаг 3. Соединение с сервером и базой данных. | Подключение к базе данных | Нет | Да |
| Шаг 4. Создание объекта SqlColumnMasterKeySettings, содержащего сведения о расположении нового главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти (PowerShell). Чтобы создать его, используйте командлет, предназначенный для вашего хранилища ключей. |
New-SqlAzureKeyVaultColumnMasterKeySettings Новые настройки мастер-ключа столбца SqlCertificateStore Новая настройка ключа мастер-колонки SqlCng New-SqlCspColumnMasterKeySettings |
Нет | Нет |
| Шаг 5. Создайте метаданные о новом главном ключе столбца в базе данных. |
New-SqlColumnMasterKey Примечание: внутренне этот командлет использует инструкцию CREATE COLUMN MASTER KEY (Transact-SQL) для создания метаданных ключа. |
Нет | Да |
| Шаг 6. Выполните проверку подлинности в Azure, если ваш текущий главный ключ столбца или новый главный ключ столбца хранится в хранилище ключей или в управляемом модуле HSM в Azure Key Vault. | Connect-AzAccount | Да | Нет |
| Шаг 7. Получите токен доступа для Azure Key Vaults, если ваш главный ключ столбца хранится в Azure Key Vault. | Get-AzAccessToken | Нет | Нет |
| Шаг 8. Начните ротацию, зашифровав каждый ключ шифрования столбца, который в данный момент защищен с помощью старого главного ключа столбца, используя новый главный ключ столбца. После выполнения этого шага каждый затронутый ключ шифрования столбца (связанный со старым сменяемым главным ключом столбца) шифруется с помощью старого и нового главных ключей и имеет два зашифрованных значения в метаданных базы данных. | Invoke-SqlColumnMasterKeyRotation | Да | Да |
| Шаг 9. Работа с администраторами всех приложений, выполняющих запросы к зашифрованным столбцам в базе данных (и защищенных с помощью старого главного ключа столбца), по обеспечению доступа приложений к новому главному ключу столбца. | Создание и хранение мастер-ключей столбцов (Всегда зашифровано) | Да | Нет |
| Шаг 10. Завершите ротацию. Примечание. Перед выполнением этого шага убедитесь, что все приложения, запрашивающие зашифрованные столбцы, защищенные старым главным ключом столбца, были настроены для использования нового главного ключа столбца. В случае преждевременного выполнения этого шага некоторые приложения не смогут расшифровывать данные. Завершите ротацию, удалив из базы данных зашифрованные значения, созданные с помощью старого главного ключа столбца. При этом будет удалена связь между старым главным ключом столбца и защищаемыми ими ключами шифрования столбцов. |
Complete-SqlColumnMasterKeyRotation | Нет | Да |
| Шаг 11. Удалите метаданные из старого главного ключа столбца. | Remove-SqlColumnMasterKey | Нет | Да |
Примечание.
Настоятельно рекомендуется не удалять навсегда старый главный ключ столбца после смены. Вместо этого, вам следует сохранить старый главный ключ столбца в его текущем хранилище ключей или архивировать его в другое надежное место. При восстановлении базы данных из файла резервной копии на момент до настройки нового главного ключа столбца вам потребуется старый ключ для доступа к данным.
Смена главного ключа столбца без разделения ролей (пример с сертификатом Windows)
Приведенный ниже скрипт является законченным примером замены существующего главного ключа столбца (CMK1) на новый главный ключ столбца (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.'
Смена основного ключа столбца с разделением ролей
В описанном в этом разделе процессе смены главного ключа столбца существует разделение ролей между администраторами безопасности и администраторами баз данных.
Внимание
Перед выполнением каких-либо действий, где в таблице ниже указано доступ к открытым текстовым ключам/хранилищу ключей=Да (шаги, которые обращаются к открытым текстовым ключам или хранилищу ключей), убедитесь, что среда PowerShell работает на защищенном компьютере, отличном от компьютера, на котором размещена база данных. Дополнительные сведения см. в разделе "Вопросы безопасности" для управления ключами.
Часть 1. Администратор баз данных
Администратор баз данных получает метаданные о подлежащем смене главном ключе столбца и о затрагиваемых ключах шифрования столбцов, которые связаны с текущим главным ключом столбца. Администратор баз данных предоставляет эти сведения администратору безопасности.
| Задача | Статья | Доступ к ключам с открытым текстом или хранилищу ключей | Доступ к базе данных |
|---|---|---|---|
| Шаг 1. Запуск среды PowerShell и импорт модуля SqlServer. | Импорт модуля SqlServer | Нет | нет |
| Шаг 2. Соединение с сервером и базой данных. | Подключение к базе данных | Нет | Да |
| Шаг 3. Получение метаданных о старом главном ключе столбца. | Get-SqlColumnMasterKey | Нет | Да |
| Шаг 4. Получение метаданных о ключах шифрования столбцов, защищенных старым главным ключом столбца, включая их зашифрованные значения. | Get-SqlColumnEncryptionKey (Ключ шифрования) | Нет | Да |
| Шаг 5. Предоставьте информацию о расположении главного ключа столбца, включая имя поставщика и путь к этому ключу, а также зашифрованные значения соответствующих ключей шифрования столбцов, защищенные с помощью старого главного ключа столбца. | См. следующие примеры. | Нет | Нет |
Часть 2. Администратор безопасности
Администратор безопасности создает новый главный ключ столбца, повторно шифрует затронутые ключи шифрования столбцов с помощью нового главного ключа столбца и предоставляет администратору баз данных сведения о новом главном ключе столбца, а также о наборе новых зашифрованных значений для затронутых ключей шифрования столбцов.
| Задача | Статья | Доступ к ключам с открытым текстом или хранилищу ключей | Доступ к базе данных |
|---|---|---|---|
| Шаг 1. Получите у администратора баз данных сведения о расположении старого главного ключа столбца и зашифрованные значения соответствующих ключей шифрования столбцов, защищенные старым главным ключом столбца. | Н/П См. следующие примеры. |
Нет | Нет |
| Шаг 2. Создайте новый главный ключ столбца в хранилище ключей. Заметка: Модуль SqlServer не поддерживает этот шаг. Для выполнения этой задачи из командной строки используйте средства, поддерживаемые выбранным хранилищем ключей. При использовании Azure Key Vault в качестве хранилища ключей смена клиентом управляемых ключей для многопользовательских клиентов не поддерживается. Убедитесь, что новый ключ, управляемый клиентом, находится в том же арендаторе, что и существующий. |
Создание и хранение главных ключей столбцов для Always Encrypted | Да | Нет |
| Шаг 3. Запуск среды PowerShell и импорт модуля SqlServer. | Импорт модуля SqlServer | Нет | Нет |
| Шаг 4. Создайте объект SqlColumnMasterKeySettings, содержащий сведения о расположении старого главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти (PowerShell). | New-SqlColumnMasterKeySettings | Нет | Нет |
| Шаг 5. Создайте объект SqlColumnMasterKeySettings, содержащий сведения о расположении нового главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти (PowerShell). Чтобы создать его, используйте командлет, предназначенный для вашего хранилища ключей. |
New-SqlAzureKeyVaultColumnMasterKeySettings Новые настройки мастер-ключа столбца SqlCertificateStore Новая настройка ключа мастер-колонки SqlCng New-SqlCspColumnMasterKeySettings |
Нет | Нет |
| Шаг 6. Выполните аутентификацию в Azure, если старый (текущий) или новый главный ключ столбца хранится в хранилище ключей или в управляемом модуле HSM в Azure Key Vault. | Connect-AzAccount | Да | Нет |
| Шаг 7. Получите токен доступа для Azure Key Vaults, если ваш главный ключ столбца хранится в Azure Key Vault. | Get-AzAccessToken | Нет | Нет |
| Шаг 8. Пере-шифруйте каждое значение ключа шифрования столбца, который в данный момент защищен старым главным ключом столбца, используя новый главный ключ столбца. |
Новый зашифрованный ключ шифрования столбца SQL Заметка: При вызове этого командлета передайте объекты SqlColumnMasterKeySettings как для старого, так и нового главного ключа столбца вместе со значением ключа шифрования столбца, который будет повторно зашифрован. |
Да | Нет |
| Шаг 9. Поделитесь с вашим администратором баз данных информацией о расположении нового главного ключа столбца (имя поставщика и путь к главному ключу столбца), а также набором новых зашифрованных значений ключей шифрования данных столбца. | См. следующие примеры. | Нет | Нет |
Примечание.
Настоятельно рекомендуется не удалять навсегда старый главный ключ столбца после смены. Вместо этого, вам следует сохранить старый главный ключ столбца в его текущем хранилище ключей или архивировать его в другое надежное место. При восстановлении базы данных из файла резервной копии на момент до настройки нового главного ключа столбца вам потребуется старый ключ для доступа к данным.
Часть 3. Администратор баз данных
Администратор баз данных создает метаданные для нового главного ключа столбца и обновляет метаданные затронутых ключей шифрования столбцов для добавления нового набора зашифрованных значений. На этом этапе администратор баз данных работает вместе с администраторами приложений, выполняющих запросы к столбцам шифрования. Администраторы приложений обеспечивают доступ приложений к новому главному ключу столбца. После настройки всех приложений для использования нового главного ключа столбца администратор баз данных удаляет старый набор зашифрованных значений и метаданные старого главного ключа столбца.
| Задача | Статья | Доступ к ключам с открытым текстом или хранилищу ключей | Доступ к базе данных |
|---|---|---|---|
| Шаг 1. Получение от администратора безопасности сведений о расположении нового главного ключа столбца и нового набора зашифрованных значений соответствующих ключей шифрования столбцов, защищенных с помощью старого главного ключа столбца. | См. следующие примеры. | Нет | Нет |
| Шаг 2. Запуск среды PowerShell и импорт модуля SqlServer. | Импорт модуля SqlServer | Нет | Нет |
| Шаг 3. Соединение с сервером и базой данных. | Подключение к базе данных | Нет | Да |
| Шаг 4. Создание объекта SqlColumnMasterKeySettings, содержащего сведения о расположении нового главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти (PowerShell). | New-SqlColumnMasterKeySettings | Нет | Нет |
| Шаг 5. Создайте метаданные о новом главном ключе столбца в базе данных. |
New-SqlColumnMasterKey Примечание: Фактически этот командлет выполняет оператор CREATE COLUMN MASTER KEY (Transact-SQL) для создания метаданных ключа. |
Нет | Да |
| Шаг 6. Извлеките метаданные о ключах шифрования столбцов, защищенных старым главным ключом столбца. | Get-SqlColumnEncryptionKey (Ключ шифрования) | Нет | Да |
| Шаг 7. Добавление нового зашифрованного значения (созданного с помощью нового главного ключа столбца) в метаданные для каждого затронутого ключа шифрования столбца. | Add-SqlColumnEncryptionKeyValue | Нет | Да |
| Шаг 8. Работа с администраторами всех приложений, выполняющих запросы к зашифрованным столбцам в базе данных (и защищенных с помощью старого главного ключа столбца), по обеспечению доступа приложений к новому главному ключу столбца. | Создание и хранение основных ключей столбца (Всегда шифруется) | Нет | Нет |
| Шаг 9. Завершите ротацию, удалив из базы данных зашифрованные значения, связанные со старым главным ключом столбца. Заметка: Перед выполнением этого шага убедитесь, что все приложения, запрашивающие зашифрованные столбцы, защищенные с помощью старого главного ключа столбца, настроены для использования нового главного ключа столбца. В случае преждевременного выполнения этого шага некоторые приложения не смогут расшифровывать данные. На этом шаге удаляется связь между старым главным ключом столбца и защищаемыми ими ключами шифрования столбцов. |
Complete-SqlColumnMasterKeyRotation Кроме того, можно использовать Remove-SqlColumnEncryptionKeyValue |
Нет | Да |
| Шаг 10. Удалите метаданные старого главного ключа столбца из базы данных. | Remove-SqlColumnMasterKey | Нет | Да |
Смена мастер-ключа столбца с разделением ролей (на примере сертификата Windows)
Приведенный ниже скрипт — законченный пример создания нового главного ключа столбца, который является сертификатом в хранилище сертификатов Windows, смены существующего (текущего) главного ключа столбца для его замены новым главным ключом столбца. В скрипте предполагается, что целевая база данных содержит главный ключ столбца с именем CMK1 (подлежащий смене), который шифрует некоторые ключи шифрования столбцов.
Часть 1. Администратор баз данных
[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"
Часть 2. Администратор безопасности
[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"
Часть 3. Администратор баз данных
[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'
Вращение ключа шифрования столбца
Процедура смены ключа шифрования столбца включает в себя действия по расшифровке данных во всех столбцах, которые зашифрованы с помощью подлежащего смене ключа, и повторному шифрованию данных с помощью нового ключа шифрования столбца. Поскольку для этого процесса требуется доступ как к ключам, так и к базе данных, его выполнение невозможно при разделении ролей. Смена ключа шифрования столбца может занимать много времени, если таблицы, содержащие столбцы, зашифрованные поворачиваемым ключом, очень большие. Поэтому организации необходимо тщательно спланировать процесс смены ключа шифрования столбца.
Ключ шифрования столбца можно сменить в двух режимах — вне сети или в сети. Первый способ быстрее, но приложения не смогут записывать данные в затронутые таблицы. Последний подход, скорее всего, займет больше времени, но можно ограничить интервал времени, в течение которого затронутые таблицы недоступны для приложений. Дополнительные сведения см. в разделе "Настройка шифрования столбцов с помощью Always Encrypted с помощью PowerShell и Set-SqlColumnEncryption".
| Задача | Статья | Доступ к ключам с открытым текстом или хранилищу ключей | Доступ к базе данных |
|---|---|---|---|
| Шаг 1. Запуск среды PowerShell и импорт модуля SqlServer. | Импорт модуля SqlServer | Нет | Нет |
| Шаг 2. Соединение с сервером и базой данных. | Подключение к базе данных | Нет | Да |
| Шаг 3. Пройдите аутентификацию в Azure, если ключ главного столбца (который защищает ключ шифрования столбца и подлежит смене) хранится в хранилище ключей или в управляемом модуле HSM в Azure Key Vault. | Connect-AzAccount | Да | Нет |
| Шаг 4. Получите токен доступа для Azure Key Vaults, если ваш главный ключ столбца хранится в Azure Key Vault. | Get-AzAccessToken | Нет | Нет |
| Шаг 5. Сгенерируйте новый ключ шифрования столбца, зашифруйте его с помощью главного ключа столбца и создайте метаданные ключа шифрования столбца в базе данных. |
New-SqlColumnEncryptionKey Заметка: Используйте вариант командлета, который создает и шифрует ключ шифрования столбца. Внутренне этот командлет выполняет инструкцию CREATE COLUMN ENCRYPTION KEY (Transact-SQL) для создания метаданных ключа. |
Да | Да |
| Шаг 6. Найдите все столбцы, зашифрованные старым ключом шифрования столбца. | Руководство по программированию объектов управления SQL Server (SMO) | Нет | Да |
| Шаг 7. Создайте объект SqlColumnEncryptionSettings для каждого затронутого столбца. SqlColumnEncryptionSettings — это объект, который существует в памяти (PowerShell). Он определяет целевую схему шифрования столбца. В этом случае объект должен указывать, что затронутый столбец следует зашифровать с помощью нового ключа шифрования столбца. | Новые настройки шифрования столбца SQL (New-SqlColumnEncryptionSettings) | Нет | Нет |
| Шаг 8. Повторное шифрование столбцов, определенных на шаге 5, с помощью нового ключа шифрования столбца. |
Set-SqlColumnEncryption Заметка: Этот шаг может занять много времени. Приложения не будут иметь доступ к таблицам во время выполнения операции или ее части в зависимости от выбранного режима (в сети или вне сети). |
Да | Да |
| Шаг 9. Удаление метаданных для старого ключа шифрования столбца. | Удалить ключ шифрования столбца SQL (Remove-SqlColumnEncryptionKey) | Нет | Да |
Пример: ротация ключа шифрования столбца
В приведенном ниже скрипте демонстрируется смена ключа шифрования столбца. В скрипте предполагается, что целевая база данных содержит несколько столбцов, зашифрованных с помощью ключа шифрования CEK1 (подлежащего смене), который защищен главным ключом столбца CMK1 (главный ключ столбца не хранится в 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'
Следующие шаги
- Запрос столбцов с помощью Always Encrypted с SQL Server Management Studio
- Разработка приложений с помощью Always Encrypted
См. также
- Always Encrypted
- Обзор управления ключами для Always Encrypted
- Настройка Always Encrypted с помощью PowerShell
- Смена ключей Always Encrypted с помощью 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)