如何清除内存中AES密钥的所有副本?或阻止.NET运行时复制密钥
我尝试使用AES进行解密,并显式清除密钥占用的内存。解密时传入密钥,完成后调用CryptographicOperations.ZeroMemory方法清零自己持有的密钥内存:
// 示例密钥,实际密钥是通过专用硬件上的非对称私钥解密得到的 byte[] key = System.Security.Cryptography.RandomNumberGenerator.GetBytes(32); using (Aes aes = Aes.Create()) { aes.Key = key; // 在此处使用aes对象执行解密操作 } CryptographicOperations.ZeroMemory(key);
但查看.NET运行时中Aes的Key属性实现,发现它通过CloneByteArray方法创建了密钥的副本:
public virtual byte[] Key { get { if (KeyValue == null) GenerateKey(); return KeyValue.CloneByteArray()!; } set { ArgumentNullException.ThrowIfNull(value); long bitLength = value.Length * 8L; if (bitLength > int.MaxValue || !ValidKeySize((int)bitLength)) throw new CryptographicException(SR.Cryptography_InvalidKeySize); // 必须将字节转换为比特数 this.KeySize = (int)bitLength; KeyValue = value.CloneByteArray(); } }
CloneByteArray扩展方法的实现如下:
public static byte[]? CloneByteArray(this byte[]? src) { if (src == null) { return null; } return (byte[])(src.Clone()); }
请问是否有办法显式清除处理AES密钥的所有内存(包括.NET运行时创建的所有副本)?或者能否阻止.NET运行时复制密钥,仅使用我自行分配的密钥?
1. 无法直接阻止.NET运行时复制密钥
标准Aes实现的Key属性设计逻辑就是克隆传入的密钥,目的是避免外部修改密钥导致加密/解密过程出现意外。因此没办法直接强制它使用你自行分配的密钥数组,必须接受副本的存在。
2. 清除.NET内部密钥副本的可行方案
方案一:使用AesCng(Windows平台专属)
如果代码运行在Windows环境下,可以使用AesCng类,它提供了对密钥内存更精细的控制:
byte[] key = System.Security.Cryptography.RandomNumberGenerator.GetBytes(32); using (var aes = new AesCng()) { // 使用ImportKeyMaterial方法,可指定不保留密钥在内存中 aes.ImportKeyMaterial(key, CngKeyBlobFormat.GenericBlob); // 执行解密操作 } CryptographicOperations.ZeroMemory(key);
AesCng依赖Windows CNG加密库,导入密钥时可通过参数控制密钥存储方式,部分场景下能减少内存中的密钥副本;且当AesCng实例被释放时,内部的密钥内存会被CNG库自动清零。
方案二:自定义Aes实现(跨平台)
如果需要跨平台支持,可以基于SymmetricAlgorithm抽象类实现自己的Aes逻辑,直接使用传入的密钥数组而不克隆。但这种方式需要自行处理密钥验证、加密/解密核心逻辑,复杂度较高,且要确保内存操作的安全性。
方案三:利用GC和内存压力(兜底方案,不推荐)
虽然无法直接访问.NET内部的KeyValue数组,但可以在使用完Aes实例后,强制触发垃圾回收,配合CryptographicOperations.ZeroMemory处理托管内存:
using (Aes aes = Aes.Create()) { aes.Key = key; // 执行解密操作 } CryptographicOperations.ZeroMemory(key); // 提示GC回收Aes实例 GC.Collect(); GC.WaitForPendingFinalizers();
但这种方式不可靠,因为GC的回收时机不确定,无法保证内部密钥副本一定会被清零,仅能作为兜底手段。
3. 额外注意事项
- 避免调用
Aes.Key的getter方法,每次调用都会创建新的密钥副本,增加内存中密钥的残留风险。 - 对于敏感密钥,尽量减少其在内存中的停留时间,用完立即清零自己持有的密钥数组。
内容的提问来源于stack exchange,提问作者johanrex

