.NET 4.7应用使用Azure.Security.KeyVault.Keys创建对称密钥问题
问题根因
报错和功能不生效来自两个核心问题:
CryptographyClient与KeyResolver仅支持Key Vault中存储的Key类型资源(资源路径格式为/keys/[密钥名称]),不支持直接读取/secrets/路径下的Secret资源,传入Secret URI会触发资源解析失败,抛出URI格式错误。- 原有旧逻辑是直接将Secret中存储的Base64字符串作为AES对称密钥完成Blob客户端加解密,没有使用Key Vault托管的非对称密钥做密钥包装,之前代码中硬编码
KeyWrapAlgorithm = "RSA-OAEP"完全不符合原有业务逻辑,且新版SDK默认的V2加密格式和旧版SDK生成的V1加密Blob不兼容,会导致解密失败。
正确实现方案
新版Azure Blob SDK支持直接传入自定义实现的密钥对象完成客户端加解密,不需要依赖Key Vault托管Key资源,只需要先读取Secret中的对称密钥值,实现IKeyEncryptionKey接口包装对称密钥逻辑,再传入加密配置即可。
依赖包要求(.NET 4.7兼容)
安装以下稳定版NuGet包,注意选择支持.NET Framework 4.7的版本:
Azure.Security.KeyVault.Secrets:用于读取Key Vault中存储的SecretAzure.Storage.Blobs:新版Blob存储基础客户端Azure.Storage.Blobs.Specialized:提供Blob客户端加密扩展能力Azure.Identity:提供Azure AD身份认证能力
步骤1:实现对称密钥包装类
新版SDK没有提供旧版SymmetricKey的内置实现,需要自己实现IKeyEncryptionKey接口,对齐旧版对称密钥的加解密逻辑:
using Azure.Core.Cryptography; using System; using System.Security.Cryptography; public class AesSymmetricKey : IKeyEncryptionKey { private readonly byte[] _keyContent; public string KeyId { get; } public AesSymmetricKey(string keyId, byte[] keyContent) { KeyId = keyId; _keyContent = keyContent; } public byte[] WrapKey(string algorithm, ReadOnlyMemory<byte> dataKey) { using var aes = Aes.Create(); aes.Key = _keyContent; aes.GenerateIV(); using var encryptor = aes.CreateEncryptor(); var encryptedKey = encryptor.TransformFinalBlock(dataKey.ToArray(), 0, dataKey.Length); // 拼接IV与加密后的数据密钥,解密时需要拆分使用 var result = new byte[aes.IV.Length + encryptedKey.Length]; Buffer.BlockCopy(aes.IV, 0, result, 0, aes.IV.Length); Buffer.BlockCopy(encryptedKey, 0, result, aes.IV.Length, encryptedKey.Length); return result; } public byte[] UnwrapKey(string algorithm, ReadOnlyMemory<byte> encryptedDataKey) { using var aes = Aes.Create(); aes.Key = _keyContent; var ivLength = aes.BlockSize / 8; var iv = new byte[ivLength]; var cipherText = new byte[encryptedDataKey.Length - ivLength]; encryptedDataKey.Slice(0, ivLength).CopyTo(iv); encryptedDataKey.Slice(ivLength).CopyTo(cipherText); aes.IV = iv; using var decryptor = aes.CreateDecryptor(); return decryptor.TransformFinalBlock(cipherText, 0, cipherText.Length); } // 本地对称密钥不需要签名/验签逻辑,直接抛出未实现异常即可 public byte[] Sign(string algorithm, ReadOnlyMemory<byte> digest) => throw new NotImplementedException(); public bool Verify(string algorithm, ReadOnlyMemory<byte> digest, ReadOnlyMemory<byte> signature) => throw new NotImplementedException(); }
步骤2:实现加密Blob下载逻辑
注意:旧版Microsoft.Azure.Storage.Blob使用V1版本客户端加密格式,配置时必须选择ClientSideEncryptionVersion.V1_0,否则会出现密文格式不兼容问题。
using Azure.Identity; using Azure.Security.KeyVault.Secrets; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Azure.Storage.Blobs.Specialized; using System; using System.IO; using System.Threading.Tasks; public async Task DownloadEncryptedBlobs(string storageConnStr, string keyVaultEndpoint, string secretName, string containerName, string blobPrefix, string localSavePath) { // 1. 从Key Vault读取存储对称密钥的Secret var secretClient = new SecretClient(new Uri(keyVaultEndpoint), new DefaultAzureCredential()); KeyVaultSecret encryptionSecret = await secretClient.GetSecretAsync(secretName); byte[] symmetricKeyBytes = Convert.FromBase64String(encryptionSecret.Value); // 2. 构造对称密钥实例 var symmetricKey = new AesSymmetricKey(secretName, symmetricKeyBytes); // 3. 配置客户端加密选项 var encryptionOptions = new ClientSideEncryptionOptions(ClientSideEncryptionVersion.V1_0) { KeyEncryptionKey = symmetricKey, KeyResolver = null // 直接使用本地对称密钥,不需要密钥解析器 }; // 4. 初始化Blob客户端 var blobClientOptions = new SpecializedBlobClientOptions { ClientSideEncryption = encryptionOptions }; var blobServiceClient = new BlobServiceClient(storageConnStr, blobClientOptions); var targetContainer = blobServiceClient.GetBlobContainerClient(containerName); // 5. 遍历指定前缀的Blob,SDK会自动完成解密下载 await foreach (BlobItem blob in targetContainer.GetBlobsAsync(prefix: blobPrefix)) { var blobClient = targetContainer.GetBlobClient(blob.Name); var localFilePath = Path.Combine(localSavePath, blob.Name.Replace(blobPrefix, "").TrimStart('/')); // 自动创建本地目录 var fileDir = Path.GetDirectoryName(localFilePath); if (!Directory.Exists(fileDir)) Directory.CreateDirectory(fileDir); await blobClient.DownloadToAsync(localFilePath); } }
注意事项
- 确保
DefaultAzureCredential使用的身份拥有Key Vault对应Secret的Get权限,以及目标Blob容器的读权限。 - 如果旧逻辑自定义了AES加密模式、填充方式,需要对应调整
AesSymmetricKey类中的加解密实现,和旧逻辑保持一致即可正常解密。 - 禁止将Secret类型资源的URI传入
CryptographyClient,该类型仅面向Key Vault托管的Key资源设计,无法读取Secret内容。
内容的提问来源于stack exchange,提问作者Shailesh Prajapati
相关产品推荐
相关产品推荐

