关于 c#:在内存中保护对称加密密钥

Securing a symmetric encryption key in memory

我有一个应用程序,我正在检索磁盘中存在的对称加密密钥并使用它来加密数据。程序启动时从磁盘中检索加密密钥,并将其作为字节数组存储在私有类变量中。在程序启动时从磁盘中检索到密钥后,在密钥上使用 ProtectedMemory.Protect() 来保护它。密钥每次需要使用时都不受 ProtectedMemory.Unprotect() 保护,使用后再次受到保护。

让我思考这个方案的有效性的部分是在从磁盘检索密钥的情况下,并且每次需要将密钥用作易于利用的漏洞时,都会在 2 个关键时刻创建程序的执行周期:当程序刚刚完成从磁盘加载密钥并且没有调用 Protect() 方法时,以及当密钥在加密期间不受保护以供使用时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ApplicationClass {
   private byte[] encKey;

   public ApplicationClass() {
     // Fetches the encryption key first
     encKey = StorageInt.FetchKey(); // Fetches and returns the encrypted key from the disk

     // A gaping vulnerability here as the key is just loaded in memory and is not protected
     ProtectedMemory.Protect(encKey, MemoryProtectionScope.SameProcess);

     // Other initialization instructions follows
   }

   private byte[] ApplySymmEnc(byte[] plaintext) {
     Aes aes = Aes.Create();
     byte[] iv = new byte[128];
     RNGCryptoServiceProvider randomBytesGenerator = new RNGCryptoServiceProvider();
     randomBytesGenerator.GetNonZeroBytes(iv);
     randomBytesGenerator.Dispose();
     ProtectedMemory.Unprotect(encKey, MemoryProtectionScope.SameProcess);

     // Another gaping vulnerability here!

     ICryptoTransform encryptor = aes.CreateEncryptor(encKey, iv);
     ProtectedMemory.Protect(encKey, MemoryProtectionScope.SameProcess); // Protect the key right after it is used for encryption

     // Instructions for encryption follows
   }
}

提前致谢。

编辑:至于在磁盘上不关心密钥安全性的原因,密钥以相当安全的模糊形式存在于磁盘中,在检索时由 StorageInt.FetchKey() 函数解密。


防御所有攻击是不可能的。您可以获得的最接近的可能是使用可信平台模块 (tpm) 芯片,因此密钥永远不会离开芯片。如果您的处理器提供了可信的执行环境,那么第二好的可能是使用可信的执行环境。但两者都无法抵御各种攻击。

如果没有专门的硬件支持,尝试保护您的程序免受管理员的侵害可能是不可行的。如果攻击者可以读取你程序的内存,为什么他不能直接从磁盘读取密钥呢?

在 ram 中加密密钥对于防止诸如冷启动攻击之类的攻击很有用,而此类攻击可能很难在易受攻击的时刻正确获取密钥。

在内存中键的最佳实践问题中也有一些可能有用的答案。


在这些时间(从磁盘获取之后但在调用 ProtectedMemory.Protect 之前)保持对称加密密钥的保护需要操作系统级别的支持,这在 Windows(和其他 .net 核心平台)中不存在。

直接来自 .net 团队关于处理凭据的类似主题:"SecureString 的目的是避免将机密作为纯文本存储在进程内存中。......但是,即使在 Windows 上,SecureString 也不会\\' t 作为操作系统概念存在...它只是使获取纯文本的窗口更短...处理凭据的一般方法是避免使用它们,而是依靠其他方式进行身份验证,例如证书或 Windows 身份验证" (https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md)


你提到了

"when the program has just finished loading the key from the disk and
has not called the Protect()"

如果我改写上面的句子 StorageInt.FetchKey() 正在为您获取一个未受保护的密钥,而您此时想要保护它。

你可以为StorageInt.FetchKey(bool IsProtectKey = true)创建一个扩展方法,这个方法可以调用ProtectedMemory.Protect()。您只能使用扩展方法来获取密钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     public int StorageInt.FetchKey(bool IsProtectKey = true)
        {
            encKey = StorageInt.FetchKey();
            CodeEncryptedKey = EncryptAndStoreProtectedKey();
            //Above function should encrypt your"encKey" to AES256 or to any secure encryption algorithm, store it in cache and return encrypted key

            ProtectedMemory.Protect(encKey, MemoryProtectionScope.SameProcess);            
        }

        Public string UseKey(string CodeEncryptedKey)
        {
            encKey = GetProtectedKeyFromCache(CodeEncryptedKey)
            ProtectedMemory.Unprotect(encKey, MemoryProtectionScope.SameProcess);
            Task.Run(() => ProtectKeyAfter5Seconds(encKey));
            return encKey;
        }

        Void ProtectKeyAfter5Seconds(encKey)
        {
            Thread.Sleep(5000); //Im telling here to encrypt after 5 seconds, you can have your logic to encrypt after one time use or any particular logic
            ProtectedMemory.Protect(encKey, MemoryProtectionScope.SameProcess);
        }

这有帮助吗?