【问题标题】:Securing a symmetric encryption key in memory保护内存中的对称加密密钥
【发布时间】:2020-05-06 07:30:22
【问题描述】:

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

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

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() 函数解密。

【问题讨论】:

  • 你的链接中最薄弱的链条是什么?例如,密钥是否在磁盘上受到保护?
  • @LasseV.Karlsen 密钥在磁盘上时保持可靠安全。最薄弱的链条,我相信,一定是帖子中提到的两种场景。
  • .net 具有用于内存中安全密钥存储的特殊 clr 类型:SecureString (docs.microsoft.com/en-us/dotnet/api/…)。我会从 Windows 中的 x509 证书存储等安全存储中加载密钥 - 它看起来很安全。

标签: c# security cryptography encryption-symmetric dpapi


【解决方案1】:

防御所有攻击是不可能的。你能得到的最接近的可能是使用Trusted Platform Module (tpm) 芯片,因此密钥永远不会离开芯片。第二个最好的方法可能是使用trusted execution environment(如果您的处理器提供)。但两者都无法抵御各种攻击。

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

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

问题best practices for keys in memory中也有一些可能有用的答案。

【讨论】:

  • 密钥虽然是磁盘之一,但相对于内存而言是相当安全的。
  • 你能激励一下或提供一个来源吗?内存通常受到其他进程的保护。我的理由是,如果攻击者有权访问进程内存,那么他应该能够执行进程正在执行的任何操作,包括读取磁盘。我的推理有问题吗?或者您是否担心像 heartbleed 这样的无意内存泄漏?
  • 我提到密钥在磁盘上是安全的原因是因为它被安全地混淆并且在加载到内存时被解密。我在这件事上主要担心的是,密钥必须是安全的/无法被所有入侵行为所破解,例如但不限于内存转储和缓冲区溢出(例如 Heartbleed,您上次评论中提到的漏洞)。
  • “安全混淆”不是一回事。如果攻击者可以访问磁盘上的密钥和程序文件,他可以逆转任何混淆。 .Net 会清除所有分配的内存,并进行检查以防止缓冲区溢出,因此泄漏数据的风险相当小。如果攻击者具有物理或管理员访问权限,那么解密密钥的短窗口可能是您最不担心的。如果攻击者实现了远程代码执行,他可以撤消您应用的任何保护。
  • 一种选择是将密码存储为加密数字。请参阅nuget.org/packages/Walter.Cypher 密码扫描程序会跳过它,因为他们会寻找除数字以外的任何内容
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    你提到了

    "当程序刚刚完成从磁盘加载密钥并且 还没有调用Protect()"

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

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

         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);
            }
    

    这有帮助吗?

    【讨论】:

    • 扩展方法中如何设置密钥保护?
    • 扩展方法应该同时调用 fetchkey() 和protect()。因此扩展方法的返回值将始终是受保护的密钥。
    • 添加您提到的扩展方法的额外层并不能解决手头的问题,其中包括在从磁盘获取并解密后立即保护密钥。
    • 更新了我的解决方案并添加了使用扩展方法的代码。检查这是否可以帮助您
    • 仍然没有解决围绕在获取新获取的密钥后立即保护内存中的密钥的问题。此外,选择加密密钥会引发另一个问题:您打算如何保护 IV 和用于加密从磁盘获取的密钥的密钥?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-25
    • 2010-10-30
    • 1970-01-01
    • 2011-02-01
    • 1970-01-01
    相关资源
    最近更新 更多