没有SecureString如何保护字符串?

8

6
为什么?你要解决的实际问题是什么?你想保护自己免受什么?如果有人在你的服务器上拥有调试权限,可以获取内存转储并检查内存内容,那你已经失败了。微软建议不使用SecureString的原因是,无论如何,所有解决方案都将在一段时间内将字符串保留为明文。即使加密该字符串,它也必须以明文形式开始才能进行加密。 - Panagiotis Kanavos
3
“即使您对字符串进行加密,它在加密之前仍必须以明文开始” - 这是不正确的。SecureString.Append 的意思是您可以从用户输入构建一个安全字符串(例如对于每个按键),而无需存储明文。 - Dai
2
@Dai,用户输入是明文。逐个按键输入输入甚至无法完全记录按键记录器。SecureString提供有限的保护,仅限于有限的范围。它不是用来保护输入过程的。使用该字符串需要提取其内容。 - Panagiotis Kanavos
@PanagiotisKanavos 信用卡号在加密之前为明文的原因是用户将其输入为明文。我们希望尽快将其从内存中清除。如果有人认为我们不应该费心:那没问题。但是Windows仍会在交给你4KB页面之前将内存清零(这样你就看不到页面上的内容),并且它仍然具有“SecureZeroMemory”。 - Ian Boyd
@IanBoyd 的论点是,.NET 团队表示 SecureString 并不安全,并提供非常有限的安全性,同时给人一种安全的错觉。原始信用卡文本将一直保存在内存中,直到被垃圾回收。一旦使用 Windows API 或任何不理解 SecureString 的东西,你就会得到字符串。 - Panagiotis Kanavos
4个回答

9

SecureString类没有替代品。微软推荐的“替代方案”在这里:https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md

处理凭据的一般方法是避免使用它们,而是依赖于其他身份验证手段,例如证书或Windows身份验证。

因此,如果确实需要凭据且没有其他方式:在.NET Framework上,使用SecureString。对于.NET Core目前没有替代方案。


7
tl;dr: SecureString是一个好的和正确的想法。微软不再推荐它的原因是因为.NET Core无法支持它,因为Linux不支持加密。
简短回答:
  • 使用字符数组
  • 对其进行加密
长篇回答:
你需要SecureString有两个原因。
第一个原因类似于HeartBleed攻击,以及SecureZeroMemory存在的原因,以及Windows在将内存页提供给你之前总是将其清零的原因:为了避免信息泄漏。
额外阅读:

终端服务将用户密码以明文形式保存在RAM中。

第二个原因是SecureString在.NET中存在的原因,是因为你没有办法调用SecureZeroMemory。在.NET中,字符串是不可变的,你无法控制它们的生命周期。
为了解决这个问题,有两个要素:
1. 你可以切换到一个字符数组。 当它是一个数组时,意味着你可以清除其内容。这意味着当你完成处理敏感的信用卡号码、比特币私钥、密码、Intel Blu-Ray主密钥时,你可以执行与ZeroMemory相当的操作:你可以将它们清除。
这解决了完全无法清除C#字符串的问题。
2. 使用CryptProtectData进行加密。
SecureString的另一个无关的优点是,原始字符串不会出现在内存转储、虚拟机RAM快照、Web服务器日志、调试器监视窗口中。或者在HeartBleed攻击中,不会出现在未初始化数据中被提供给其他网站用户的情况下。
这是SecureString提供的两个核心要素。
而且你可以复制它们。.NET Core没有这些要素并不是因为SecureString是一个坏主意,或者是一个浪费的主意,或者是一个不完整的主意,或者“不能实现其承诺”。相反,这是因为Linux没有等效的CryptProtectData。由于.NET Core必须跨平台,他们必须迎合最低公共分母。所以他们举起双手说去掉它。
但是SecureString作为一个概念和以下内容一样有效:
- ZeroMemory - SecureZeroMemory - Windows在将一页RAM交给进程之前将其清零 - 当你SetFileValidData时,Windows将文件内容清零
任何声称相反的人都是在彻头彻尾地撒谎。
提醒 - 你想使用SecureString
你想使用SecureString

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

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

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

但我们必须认识到:

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

这些都是良好的深度防御措施。

任何说法相反的人都是错误的。


2
不,原因是它的使用非常有限,在大多数情况下,字符串要么保留在内存中,要么重新出现。原始字符串会一直保留在内存中,直到进行垃圾回收。由于SecureString在Win32 API(或Linux)中没有对应项,因此一旦应用程序尝试执行任何操作,原始字符串将重新出现。即使在SDK中,只有NetworkCredentials正确地使用了它,并且SecureString不是Windows概念,因此一旦您转到Windows API,它就会被转换回来。 - Panagiotis Kanavos
4
微软没有将SecureString移植到Linux是因为这样做并不被推荐,而不是反过来。"Shawn,我们根本不相信它在现实世界中有任何保护作用。在某个时刻,为了使用它,它会被转换回字符串,这时就不存在任何保护了。多年来我们一直没有推荐使用它。" 推特链接 - Panagiotis Kanavos
1
实际上,与其他传统类型相比,SecureString 被认为是有害的。 [与 SecureString 相比,类型的存在导致使用它并随后失败审核的客户遭受积极的伤害。](从 GH 问题中淘汰该类) - Panagiotis Kanavos
@PanagiotisKanavos 如果真的有人认为SecureString不好,那么我们可以创建一个新类:a)将字符串存储在非托管内存中,b)使用CryptProtectMemory加密它。我们可以称这个类为ProtectedString。这样,任何抱怨SecureString的人都可以错了而且满意我们已经做到了他们要求的事情。奖励,“.NET在所有环境中都不支持加密,要么是由于缺少API,要么是由于密钥管理问题。” 这是其他环境的问题,而不是试图使用加密的人的问题。 - Ian Boyd

7
我并不认为微软“不鼓励使用 SecureString” - 这是一种过度简化的说法。实际原因在于此页面中给出(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,则没问题。引用如下(重点是我的):

The contents of the array is unencrypted except on .NET Framework.

顺便说一下,如果只通过使用其 Append 方法直接将密码读入 SecureString 中,就可以安全地使用 SecureString 避免内存中的明文。当从控制台读取密码时,这非常有用(伪代码):
Console.WriteLine( "Enter your password" );
SecureString password = new SecureString();
while( Char c = Console.ReadKey() != '[Enter'] ) {
    password.Append( c );
}

......然而,如果之后您需要访问明文版本的字符串,则它就不太安全了(尽管希望垃圾收集器将明文字符串作为第0代对象收集起来)。

关于您的建议:

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

这正是SecureString已经实现的方式,但它仍然存在相同的问题:加密内容的明文副本仍然会在内存中存在一段时间 - 这才是问题所在。


嗨Dai。你能调整一下我看到的你答案中的小差异吗?“我认为SecureString是安全的......但仅适用于Windows上的.NET Framework。” ,同时还有“或者将.NET Core针对Windows进行定位-然后您就很好了”。所以我想我们都同意,在Linux上使用dotnetcore......不如我们希望的那样好。但是在Windows上使用dotnetCORE......就是分岔路口。 - granadaCoder
我认为这个问题的一些问题是,我认为有三种而不是两种排列组合。 "2" "dotnet framework" vs "dot net core"。但我认为有3个排列组合。 (1) .NET FW适用于Windows (2) .NET Core运行在Windows上(3) .NET Core不适用于Windows。我认为(但正在征求您的意见以及关于差异的我的早期请求)是"(2) .NET Core for (running on) Windows" 仍然提供了SecureString的价值(当然只有在正确使用的情况下)。我说了10年(在.NET Core存在之前),微软应该将其命名为"KindaSecureString" :) - granadaCoder
@granadaCoder 我的回答中没有任何差异或矛盾:只要你在Windows上使用你的SecureString代码,它就是最安全的 - 相比之下,在Linux和macOS上根本不安全。 - Dai
@granadaCoder 我在 ".NET Framework for Windows" 中加入了限定词,因为在 .NET Core 于 2016 年发布之前,还有其他平台也被称为 ".NET Framework",用于非 Windows 平台,例如 .NET Micro Framework、.NET/XNA for Xbox、.NET Compact Framework、Xamarin/Mono(在 .NET Core 发布之前),以及其他平台。 - Dai
1
这与Windows无关。.NET团队不建议使用SecureString,因为它给人一种虚假的安全感,而实际上并没有太多保障。我们根本不相信它在现实世界中有任何保护措施。在某些时候,为了使用它,它会被转换回字符串,那么所有的保护都失效了。多年来我们一直没有推荐使用它是因为SecureString不是Windows概念,所以一旦你进入Windows API,它就会被转换回去。 - Panagiotis Kanavos
显示剩余5条评论

5
所以你问的基本问题是“由于Microsoft不鼓励使用SecureString,我能自己实现吗?”除了你的实现很可能比Microsoft的原版不安全之外,它还将“至少”有相同的问题,因为这些问题不是特定实现的问题,而是概念问题。如果您想使用此概念,您可以使用SecureString。解决方案是不要在内存中使用加密凭据的概念,无论是使用Microsoft的类还是自己编写的程序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接