如何在没有 SecureString 的情况下保护字符串?

Fra*_*ghi 5 c# encryption securestring

用例是在 c# 中保护内存编程中的字符串。Microsoft 本身不鼓励使用 SecureString 类 ( https://docs.microsoft.com/en-us/dotnet/api/system.security.securestring?view=netframework-4.7.2 )。

我想知道它是否可以作为一个有效的替代方案:

  • 将字符串转换为字节数组并立即将字符串设置为 null(并最终调用垃圾收集器),
  • 使用类 ProtectedMemory 加密字节数组。

有什么建议吗?

Pat*_*man 8

除了SecureString班级别无选择。微软鼓励的“替代”可以在这里找到:

处理凭证的一般方法是避免使用凭证,而是依靠其他方式进行身份验证,例如证书或 Windows 身份验证。

因此,如果您确实需要凭据并且别无他法:在 .NET Framework 上,请使用SecureString. 对于 .NET Core,目前别无选择。


Ian*_*oyd 7

tl;dr:SecureString是一个好且正确的想法。微软不再推荐它的原因是.NET Core不能拥有它,因为Linux不支持加密。

简答

  • 使用字符数组
  • 加密它

长答案

您需要SecureString有两个原因。

第一个与 HeartBleed 攻击类似,也是SecureZeroMemory存在的原因,也是 Windows在将内存页面提供给您之前始终将其归零的原因:以避免泄漏信息。

奖励阅读

终端服务将未加密的用户密码保存在 RAM 中。

第二个,也是 .NET 中存在 SecureString 的原因,是因为您无法调用SecureZeroMemory。.NET 中的字符串是不可变的,您无法控制它们的生命周期。

因此,要解决这个问题,有两个要素:

  1. 您可以切换到Char 数组

当它是一个数组时,意味着你可以擦除其中的内容。这意味着你可以做道德上相当于ZeroMemory处理完敏感信用卡号、比特币私钥、密码、英特尔蓝光主密钥后的操作:您可以擦除它们

这解决了完全无法擦除C#字符串的问题

  1. 加密方式CryptProtectData

SecureString 的另一个不相关的优点是原始字符串不会出现在内存转储、虚拟机 RAM 快照、Web 服务器日志、调试器监视窗口中。或者在HeartBleed 攻击的情况下,不会出现在分发给其他网站用户的未初始化数据中。

这是SecureString的两个核心元素

两者都可以复制。.NET Core 没有它们的原因并不是因为 SecureString 是一个坏主意,或者是一个浪费的想法,或者是一个不完整的想法,或者“没有兑现它的承诺”。相反,这是因为 Linux 没有相当于CryptProtectData. 由于 .NET Core 必须是跨平台的,因此它们必须满足最低的共同点。所以他们举手说把它拿走。

但 SecureString 作为一个概念同样有效:

  • 零内存
  • 安全零内存
  • Windows 在将内存页面分配给进程之前将其清零
  • 为什么当您 SetFileValidData 时,Windows 将文件内容归零

任何持相反观点的人都是彻头彻尾的谎言。

提醒 - 您想要使用 SecureString

您想使用 SecureString

这是否意味着信用卡号在某个时刻以明文形式存在于内存中:

不,原因是它的用途非常有限,并且在大多数情况下,该字符串要么保留在内存中,要么重新出现。原始字符串保留在内存中,直到被GCd。由于 SecureString 在 Win32 API(或 Linux)中没有对应项,因此一旦应用程序尝试对其执行任何操作,原始字符串就会重新出现。即使在 SDK 中,也只有 NetworkCredentials 正确使用了它,而 SecureString 不是 Windows 概念,因此一旦您使用 Windows API,它就会被转换回来。

  • 是的,信用卡号在某个时刻以明文形式存在于内存中。
  • 是的,用户的 Bitlocker 密码在某个时候就存在于内存中。
  • 是的,iOS 用户的 PIN 码曾经在内存中。
  • 是的,比特币私钥在某个时候就在内存中。
  • 是的,加密页面文件中的数据在某些时候在 RAM 中未加密。

但我们必须认识到的是:

  • 这并不意味着你不应该从记忆中删除它
  • 这并不意味着您不应该阻止它出现在日志文件中
  • 这并不意味着您不应该阻止它出现在交换文件中
  • 这并不意味着您不应该阻止它出现在故障转储文件中
  • 这并不意味着您不应该阻止它出现在监视窗口中
  • 这并不意味着您不应该阻止它出现在快照中

这些都是很好的纵深防御措施。

任何不同的说法都是错误的。

  • Microsoft 没有将 SecureString 移植到 Linux 上,因为不推荐这样做,反之亦然。“肖恩,我们根本不相信它在现实世界中有任何保护。在某些时候,为了习惯,它会变回字符串,所有的赌注都会被取消。我们已经很多年没有推荐它了。`[tweet](https://twitter.com/blowdart/status/1379145789407039490) (4认同)
  • 不,原因是它的用途非常有限,并且在大多数情况下,该字符串要么保留在内存中,要么重新出现。原始字符串保留在内存中,直到被GCd。由于 SecureString 在 Win32 API(或 Linux)中没有对应项,因此一旦应用程序尝试对其执行任何操作,原始字符串就会重新出现。即使在 SDK 中[只有 NetworkCredentials 正确使用了它](https://twitter.com/blowdart/status/1379146105049423872) 并且“SecureString 不是 Windows 概念,因此一旦您使用 Windows API,它就会转换回来。 ` (2认同)

Dai*_*Dai 6

我不会说它“被微软劝阻”——这过于简单化了。此页面中给出了实际原因(https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md)并且论点似乎是“不值得努力使用它在 .NET Core 中”,并不是说它总体上不安全。

我认为这SecureString 安全的……但仅适用于 Windows 上的 .NET Framework。我链接到的页面来自跨平台的 .NET Core 项目 - 因此不鼓励或禁止SecureString在 .NET Core 中使用是有意义的- 但如果您的项目面向 .NET Framework(Windows 独有)或者针对.NET核心窗口-那么你的罚款。报价如下(强调我的):

除 .NET Framework 外,数组的内容未加密。

顺便说一句,SecureString如果您仅SecureString使用其Append方法直接将机密直接读入,则可以安全地使用以避免内存中的明文。这在从控制台读取密码(伪代码)时最有用:

Console.WriteLine( "Enter your password" );
SecureString password = new SecureString();
while( Char c = Console.ReadKey() != '[Enter'] ) {
    password.Append( c );
}
Run Code Online (Sandbox Code Playgroud)

...但是,如果您之后需要访问字符串的明文版本,那么它就不那么安全了(尽管明文字符串有望被 GC 作为第 0 代对象收集)。

关于你的提议:

  • 将字符串转换为字节数组并立即将字符串设置为 null(并最终调用垃圾收集器)
  • 使用类 ProtectedMemory 加密字节数组。

这正是 SecureString 的工作方式,但它仍然存在相同的问题:加密内容的明文副本仍然存在于内存中一小段时间——这就是问题所在。


nvo*_*igt 5

因此,您要问的基本问题是“既然 Microsoft 不鼓励 SecureString 用户,我可以推出自己的吗?”。

好吧,除了您的实现可能不如 Microsoft 原始版本安全之外,它至少也会遇到相同的问题,因为它们不是与具体实现有关,而是与概念有关。

如果你想使用这个概念,你也可以使用 SecureString。解决方案是不要在内存中使用加密凭据的概念,无论是 Microsoft 的类还是您自己的自制程序。