【问题标题】:How to protect strings without SecureString?如何在没有 SecureString 的情况下保护字符串?
【发布时间】:2021-06-03 00:15:06
【问题描述】:

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

我想知道它是否可以有效替代:

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

有什么建议吗?

【问题讨论】:

  • 为什么?您要解决的实际问题是什么?你想保护什么?如果有人在您的服务器上具有调试权限,能够进行内存转储并检查内存内容,那么您已经迷路了。 Microsoft 建议不要使用 SecureString 的原因是 all 解决方案会将字符串作为明文在内存中保留一段时间,无论多小。即使你加密了字符串,它也必须以明文开始,然后才能被加密
  • @PanagiotisKanavos “即使你加密了字符串,它也必须以明文开头,然后才能加密” - 这是不正确的。 SecureString.Append 意味着您可以从用户输入(例如,每次按键)构造一个安全字符串,而无需存储明文。
  • @Dai 并且用户输入是明文。一次输入一个按键甚至不会完整的键盘记录器。 SecureString 在有限的范围内提供有限的保护。这并不是为了保护输入过程。 使用该字符串也需要提取内容
  • @PanagiotisKanavos 信用卡号在加密之前是明文的原因是用户以明文形式输入。我们希望确保尽快将其从内存中删除。如果论点是我们永远不应该打扰:那很好。但是 Windows 在给你一个 4KB 页面之前仍然是零内存(所以你看不到之前页面上的内容),它仍然有 SecureZeroMemory
  • @IanBoyd 的论点是 .NET 团队说 SecureString 不安全并且提供非常有限的安全性,同时给人一种安全的假象。原始信用卡文本将在内存中,直到它被 GC'd。一旦你使用了 Windows API,或者任何不理解 SecureString 的东西,你就会得到这个字符串。

标签: c# encryption securestring


【解决方案1】:

除了SecureString 类,别无选择。微软鼓励的“替代”是here

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

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

【讨论】:

    【解决方案2】:

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

    我认为SecureString 安全的...但仅适用于 Windows 上的 .NET Framework。我链接到的页面来自跨平台的 .NET Core 项目 - 因此不鼓励或禁止在 .NET Core 中使用 SecureString 是有意义的 - 但如果您的项目针对 .NET Framework(这是专有的到 Windows)或者是针对 Windows 的 .NET Core for - 那么你很好。引用如下(强调我的):

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

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

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

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

    关于您的建议:

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

    这正是 SecureString 的工作原理,它仍然存在同样的问题:加密内容的明文副本仍然在内存中存在很短的一段时间 - 这就是问题所在。

    【讨论】:

    • 嗨戴。您能否调整我在您的答案中看到的这个小差异。 “我认为 SecureString 是安全的……但仅适用于 Windows 上的 .NET Framework。”而且“或者是针对 Windows 的 .NET Core - 那么你很好”。所以我认为我们都同意 linux 上的 dotnetcore ......是“不如我们希望的那么好”。但是 dotnetCORE ON windows..是岔路口。
    • 我认为围绕这​​个的一些问题是我认为有三个,而不是两个排列。 “2”“dotnet framework”与“dot net core”。但我认为有3个排列。 (1) .NET FW for Windows (2) .NET Core for (running-on) Windows (3) .NET Core not-for (not-running-on) Windows。而且我认为(但我询问您的意见和我对差异的早期要求)是“(2).NET Core for(运行在)Windows”仍然为 SecureString 提供价值(如果使用得当)。我已经说过 10 年(在 .net 核心存在之前)m$ 应该将其命名为“KindaSecureString”:)
    • @granadaCoder 我的回答没有任何差异或矛盾:如果您的 SecureString 代码在 Windows 上使用,它是安全的 - 与在 Linux 和 macOS 上完全不安全相比.
    • 我同意你刚刚发表的评论。但是,您的答案是“但仅适用于 Windows 上的 .NET Framework。”(强调“仅”和“Windows 上的框架”)。这就是我看到差异的地方。
    • @granadaCoder 我使用“.NET Framework for Windows”对其进行了限定,因为在 2016 年发布 .NET Core 之前,有(并且现在)其他平台也被称为非 Windows 的“.NET Framework” .NET Micro Framework、.NET/XNA for Xbox、.NET Compact Framework、Xamarin/Mono(在 .NET Core 发布之前)等平台。
    【解决方案3】:

    所以你要问的基本问题是“既然微软不鼓励使用 SecureString,我可以自己动手吗?”。

    嗯,除了您的实现可能不如 Microsoft 的原始版本安全之外,它还会至少共享相同的问题,因为它们与特定的实现无关,而是与这个概念。

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

    【讨论】:

      【解决方案4】:

      tl;dr:SecureString 是个好主意。微软不再推荐的原因是 .NET Core 做不到,因为 Linux 不支持加密。

      简答

      • 使用字符数组
      • 加密

      长答案

      您需要 SecureString 的原因有两个。

      第一个类似于 HeartBleed 攻击,SecureZeroMemory 存在的原因,以及 Windows 在将内存页提供给您之前总是清零的原因:避免信息泄露。

      额外阅读

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

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

      所以为了解决这个问题有两个要素:

      1. 您可以切换到字符数组

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

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

      1. CryptProtectData加密

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

      这是 SecureString 提供的两个核心元素。

      两者都可以复制。 .NET Core 没有它们的原因并不是因为 SecureString 是一个坏主意、一个浪费的想法、一个不完整的想法,或者 “没有兑现它的承诺”。相反,这是因为 Linux 没有 CryptProtectData 的等价物。而且由于 .NET Core 必须是跨平台的,它们必须迎合最低的公分母。所以他们举起手说把它拿走。

      但 SecureString 与以下概念一样有效:

      • 零内存
      • SecureZeroMemory
      • Windows 在将一页 RAM 分配给您的进程之前将其清零
      • 为什么当您设置文件有效数据时,Windows 会将文件内容归零

      任何说其他话的人都是在撒谎。

      提醒 - 您要使用 SecureString

      您想使用 SecureString

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

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

      • 是的,信用卡号在某些时候以明文形式保存在内存中。
      • 是的,用户的 bitlocker 密码曾在某个时间点在内存中。
      • 是的,iOS 用户的 PIN 曾经在内存中。
      • 是的,BitCoin 私钥在某个时间点在内存中。
      • 是的,加密页面文件中的数据在某个时间点在 RAM 中未加密。

      但我们必须意识到的是:

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

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

      而且任何人说不同都是完全错误的。

      【讨论】:

      • 不,原因是它的用途非常有限,在大多数情况下,字符串要么保留在内存中,要么重新出现。原始字符串保留在内存中,直到它被 GCd。由于 SecureString 在 Win32 API(或 Linux)中没有对应物,因此一旦应用程序尝试对其执行任何操作,原始字符串就会重新出现。即使在 SDK 中 only NetworkCredentials used it properlySecureString isn't a windows concept, so once you went down to a Windows API it was converted back.
      • Microsoft 没有将 SecureString 移植到 Linux 上,因为不建议这样做,反之亦然。 Shawn we simply don't believe it has any protection in the real world. At some point, to get used, it gets turned back into a string and all bets are off. We haven't recommended it in years.tweet
      • 事实上,与其他传统类型相比,SecureString 被认为是有害的。 Contrast this against SecureString, where the existence of the type is causing active harm to customers who use it and subsequently fail audits.(从GH问题到淘汰类)
      • @PanagiotisKanavos 如果确实有人认为SecureString 不好,那么我们可以创建一个新类:a) 将字符串存储在非托管内存中,b) 使用CryptProtectMemory 对其进行加密。我们可以调用这个类ProtectedString。这样一来,任何抱怨 SecureString 的人都可能是错的并且满意我们已经完成了他们所要求的。奖励,“.NET 不支持所有环境中的加密,无论是由于缺少 API 还是密钥管理问题。” 这是其他环境的错误,而不是人们试图使用 加密。
      猜你喜欢
      • 1970-01-01
      • 2020-09-13
      • 1970-01-01
      • 2017-06-04
      • 2021-11-29
      • 1970-01-01
      • 2021-09-20
      • 2013-03-26
      • 1970-01-01
      相关资源
      最近更新 更多