教程:使用 Azure 密钥保管库加密和解密 Blob

本教程介绍如何使用客户端加密通过存储在 Azure 密钥保管库中的密钥来加密和解密 Blob。

Azure Blob 存储支持服务端和客户端加密。 对于大多数场景,Azure 建议使用服务端加密功能,以便于保护数据。 若要详细了解服务端加密,请参阅静态数据的 Azure 存储加密

适用于 .NET 的 Azure Blob 存储客户端库支持在将客户端数据上传到 Azure 存储之前先在应用程序中对其进行加密,并在将数据下载到客户端时对其进行解密。 该库还支持与 Azure 密钥保管库集成以进行密钥管理。

本教程演示如何:

  • 配置对 Azure 密钥保管库资源的权限
  • 创建一个控制台应用程序以使用 .NET 客户端库来与资源交互
  • 将密钥添加到密钥保管库
  • 使用存储在密钥保管库中的密钥配置客户端加密选项
  • 创建启用客户端加密的 Blob 服务客户端对象
  • 上传已加密的 Blob,然后下载并解密 Blob

先决条件

向 Microsoft Entra 用户分配角色

在本地开发时,请确保访问密钥保管库的用户帐户拥有正确的权限。 需要有密钥保管库加密主管角色才能创建密钥并对密钥保管库中的密钥执行操作。 可使用 Azure 门户、Azure CLI 或 Azure PowerShell 向用户分配 Azure RBAC 角色。 可以在范围概述页上详细了解角色分配的可用范围。

在此方案中,你将为用户帐户分配权限(范围限定为密钥保管库)以遵循最低特权原则。 这种做法仅为用户提供所需的最低权限,并创建更安全的生产环境。

以下示例演示如何将“密钥保管库加密主管”角色分配到用户帐户,该角色提供完成本教程所需的访问权限。

重要

在大多数情况下,角色分配在 Azure 中传播需要一两分钟的时间,但极少数情况下最多可能需要 8 分钟。 如果在首次运行代码时收到身份验证错误,请稍等片刻再试。

  1. 在 Azure 门户中,使用主搜索栏或左侧导航找到你的密钥保管库。

  2. 在密钥保管库概述页上,从左侧菜单中选择“访问控制 (IAM)”。

  3. 在“访问控制 (IAM)”页上,选择“角色分配”选项卡。

  4. 从顶部菜单中选择“+ 添加”,然后从出现的下拉菜单中选择“添加角色分配”。

    A screenshot showing how to assign a role in Azure portal.

  5. 使用搜索框将结果筛选为所需角色。 对于此示例,请搜索“密钥保管库加密主管”并选择匹配结果,然后选择“下一步”。

  6. 在“访问权限分配对象”下,选择“用户、组或服务主体”,然后选择“+ 选择成员”。

  7. 在对话框中,搜索 Microsoft Entra ID 用户名(通常是 user@domain 电子邮件地址),然后选中对话框底部的“选择”。

  8. 选择“查看 + 分配”转到最后一页,然后再次选择“查看 + 分配”完成该过程。

设置项目

  1. 在控制台窗口(例如 PowerShell 或 Bash)中,使用 dotnet new 命令创建名为 BlobEncryptionKeyVault 的新控制台应用。 此命令将创建包含单个源文件的简单“Hello World”C# 项目:Program.cs

    dotnet new console -n BlobEncryptionKeyVault
    
  2. 切换到新建的 BlobEncryptionKeyVault 目录。

    cd BlobEncryptionKeyVault
    
  3. 在所需的代码编辑器中打开项目。 若在以下位置打开项目:

    • Visual Studio - 请找到并双击 BlobEncryptionKeyVault.csproj 文件。
    • Visual Studio Code - 请运行以下命令:
    code .
    

若要在此示例中与 Azure 服务交互,请使用 dotnet add package 安装以下客户端库。

dotnet add package Azure.Identity
dotnet add package Azure.Security.KeyVault.Keys
dotnet add package Azure.Storage.Blobs

添加以下 using 指令并确保将对 System.Configuration 的引用添加到项目。

using Azure;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using Azure.Storage;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;

设置环境变量

此应用程序查找名为 KEY_VAULT_NAME 的环境变量以检索密钥保管库的名称。 若要设置环境变量,请打开控制台窗口,并按照适用于你的操作系统的说明操作。 将 <your-key-vault-name> 替换为你的密钥保管库名称。

Windows:

可以从命令行设置适用于 Windows 的环境变量。 但是,在使用此方法时,该操作系统上运行的所有应用程序都可以访问这些值,如果不仔细设置,可能会导致冲突。 可以在用户或系统级别设置环境变量:

setx KEY_VAULT_NAME "<your-key-vault-name>"

在 Windows 中添加环境变量后,必须启动命令窗口的新实例。 如果在 Windows 上使用 Visual Studio,可能需要在创建环境变量后重新启动 Visual Studio 才能检测到更改。

Linux:

export KEY_VAULT_NAME=<your-key-vault-name>

在 Azure 密钥保管库中添加密钥

在此示例中,我们创建一个密钥并使用 Azure 密钥保管库客户端库将其添加到密钥保管库。 也可以使用 Azure CLIAzure 门户PowerShell 创建密钥并将其添加到密钥保管库。

在以下示例中,我们将为指定的保管库创建一个 KeyClient 对象。 KeyClient 对象将用于在指定的保管库中创建新的 RSA 密钥。

var keyName = "testRSAKey";
var keyVaultName = Environment.GetEnvironmentVariable("KEY_VAULT_NAME");

// URI for the key vault resource
var keyVaultUri = $"https://{keyVaultName}.vault.azure.cn";

TokenCredential tokenCredential = new DefaultAzureCredential();

// Create a KeyClient object
var keyClient = new KeyClient(new Uri(keyVaultUri), tokenCredential);

// Add a key to the key vault
var key = await keyClient.CreateKeyAsync(keyName, KeyType.Rsa);

创建密钥和密钥解析程序实例

接下来,我们使用刚刚添加到保管库的密钥来创建加密客户端和密钥解析程序实例。 CryptographyClient 实现 IKeyEncryptionKey,用于通过存储在 Azure 密钥保管库中的密钥执行加密操作。 KeyResolver 实现 IKeyEncryptionResolver,它从密钥标识符中检索密钥加密密钥并解析密钥。

// Cryptography client and key resolver instances using Azure Key Vault client library
CryptographyClient cryptoClient = keyClient.GetCryptographyClient(key.Value.Name, key.Value.Properties.Version);
KeyResolver keyResolver = new (tokenCredential);

如果保管库中有一个可用于加密的现有密钥,则你可以通过传入 URI 来创建密钥和密钥解析程序实例:

var keyVaultKeyUri = $"https://{keyVaultName}.vault.azure.cn/keys/{keyName}";
CryptographyClient cryptoClient = new CryptographyClient(new Uri(keyVaultKeyUri), tokenCredential);

配置加密选项

现在我们需要配置用于 Blob 上传和下载的加密选项。 若要使用客户端加密,请先创建一个 ClientSideEncryptionOptions 对象并在创建客户端时为其设置 SpecializedBlobClientOptions

ClientSideEncryptionOptions 类提供用于通过客户端加密连接到 Blob 存储的客户端配置选项。 KeyEncryptionKey 对于上传操作是必需的,用于包装生成的内容加密密钥。 KeyResolver 对于下载操作是必需的,它提取正确的密钥加密密钥来解包下载的内容加密密钥。 KeyWrapAlgorithm 对于上传操作是必需的,它指定在包装内容加密密钥时要使用的算法标识符。

重要

由于版本 1 中存在安全漏洞,建议为版本参数使用 ClientSideEncryptionVersion.V2_0 来构造 ClientSideEncryptionOptions 对象。 若要详细了解如何缓解应用中的漏洞,请参阅缓解应用程序中的安全漏洞。 有关此安全漏洞的详细信息,请参阅 Azure 存储更新 SDK 中的客户端加密以解决安全漏洞

// Configure the encryption options to be used for upload and download
ClientSideEncryptionOptions encryptionOptions = new (ClientSideEncryptionVersion.V2_0)
{
    KeyEncryptionKey = cryptoClient,
    KeyResolver = keyResolver,
    // String value that the client library will use when calling IKeyEncryptionKey.WrapKey()
    KeyWrapAlgorithm = "RSA-OAEP"
};

// Set the encryption options on the client options.
BlobClientOptions options = new SpecializedBlobClientOptions() { ClientSideEncryption = encryptionOptions };

配置客户端对象以使用客户端加密

在此示例中,我们将对 BlobServiceClient 对象应用客户端加密配置选项。 在服务客户端级别应用时,这些加密选项将从服务客户端传递到容器客户端,并从容器客户端传递到 Blob 客户端。 当 BlobClient 对象执行上传或下载操作时,Azure Blob 存储客户端库将使用信封加密在客户端加密和解密 Blob。 信封加密使用一个或多个其他密钥来加密密钥。

// Create a blob client with client-side encryption enabled.
// Attempting to construct a BlockBlobClient, PageBlobClient, or AppendBlobClient from a BlobContainerClient
// with client-side encryption options present will throw, as this functionality is only supported with BlobClient.
Uri blobUri = new (string.Format($"https://{accountName}.blob.core.chinacloudapi.cn"));
BlobClient blob = new BlobServiceClient(blobUri, tokenCredential, options).GetBlobContainerClient("test-container").GetBlobClient("testBlob");

加密 Blob 和上传

BlobClient 对象调用 upload 方法时,将执行多个步骤来进行客户端加密:

  1. Azure 存储客户端库生成 16 字节的随机初始化向量 (IV) 和 32 字节的随机内容加密密钥 (CEK),并使用此信息对 Blob 数据执行信封加密。
  2. 使用 CEK 加密 Blob 数据。
  3. 然后,使用 ClientSideEncryptionOptions 中指定的密钥加密密钥 (KEK) 来包装(加密)CEK。 在此示例中,KEK 是存储在指定 Azure 密钥保管库资源中的非对称密钥对。 Blob 客户端本身永远无法访问 KEK,它只是调用密钥保管库提供的密钥包装算法。
  4. 然后加密的 Blob 数据将上传到存储帐户。

添加以下代码以加密 Blob 并将其上传到 Azure 存储帐户:

// Upload the encrypted contents to the blob
Stream blobContent = BinaryData.FromString("Ready for encryption, Captain.").ToStream();
await blob.UploadAsync(blobContent);

上传 Blob 后,可以查看存储帐户中的 Blob,以查看加密的内容以及加密元数据。

解密 Blob 并下载

Azure 存储客户端库假设用户在本地或在密钥保管库中管理 KEK。 用户不需要知道用于加密的特定密钥。 ClientSideEncryptionOptions 中指定的密钥解析程序用于在下载和解密 Blob 数据时解析密钥标识符。

BlobClient 对象调用 download 方法时,将执行多个步骤来解密已加密的 Blob 数据:

  1. 客户端库从存储帐户下载已加密的 Blob 数据,包括加密元数据。
  2. 然后使用 KEK 解包(解密)包装的 CEK。 在此过程中,客户端库无权访问 KEK,它只能调用 ClientSideEncryptionOptions 中指定的密钥解包算法。 RSA 密钥对的私钥保留在密钥保管库中,因此 Blob 元数据中包含 CEK 的已加密密钥将发送到密钥保管库进行解密。
  3. 客户端库使用 CEK 解密已加密的 Blob 数据。

添加以下代码以下载并解密先前上传的 Blob。

// Download and decrypt the encrypted contents from the blob
Response<BlobDownloadInfo>  response = await blob.DownloadAsync();
BlobDownloadInfo downloadInfo = response.Value;
Console.WriteLine((await BinaryData.FromStreamAsync(downloadInfo.Content)).ToString());

后续步骤

在本教程中,你已了解如何使用 .NET 客户端库针对 Blob 上传和下载操作执行客户端加密。

有关 Blob 客户端加密的概述,包括有关将已加密数据迁移到版本 2 的说明,请参阅 Blob 的客户端加密

有关 Azure 密钥保管库的详细信息,请参阅 Azure 密钥保管库概述页