SecureString如何被“加密”并仍然可用?

37
根据MSDN SecureString 的说明,其内容被加密以提高安全性,这样如果程序被交换到磁盘上,则无法嗅探字符串内容。
我在想,这样的加密是如何实现的呢?算法必须是固定的,因此可能是众所周知或可推导的(比如在行业中广泛使用的七种算法之一),而且程序中必须有一个密钥。因此,攻击者可以获取加密字符串、密钥,然后解密数据。
那么,这样的加密有什么用处呢?

它并没有说是安全的,只是更加安全。 - Mark Peters
@Mark Peters:这就是我所说的“额外安全”。 - sharptooth
你可以在加密货币领域的SE网站上提问,那里非常适合这个话题。 - Rory Alsop
4个回答

21

我引用了一篇关于 DPAPI 的文章,它用于推导密钥。这应该能回答你对 SecureString 的大部分问题。

是的,SecureString 有缺点,不完全安全,有方法可以访问数据,例如,在 MSDN 上提到注入 Hawkeye 到进程中是一种提取 SecureString 的方法。我个人没有验证这个说法。

DAPI 密钥管理

DAPI 是一种基于对称加密技术,这意味着它使用相同的密钥来加密和解密数据。在介绍如何使用 DAPI 的示例之前,值得一提的是 DAPI 如何管理其密钥。大多数情况下,DAPI 密钥管理过程是隐形的,您通常不需要担心它,这也是 DAPI 是一个好方法的主要原因。

在介绍中,我写道主密钥是从用户的登录密码生成的。这并不是完整的图片。实际上,Windows 使用用户的登录密码生成主密钥。这个主密钥受到用户密码的保护,然后与用户配置文件一起存储。然后,这个主密钥被用来推导出许多其他密钥,正是这些其他密钥被用于保护数据。

Windows 之所以这样做,是因为它允许应用程序向生成单个密钥的过程添加附加信息,称为熵。您可以看到,如果每个在用户登录帐户下运行的应用程序都使用相同的密钥,那么每个应用程序都可以解除 DAPI 保护的数据。有时您可能希望应用程序能够共享 DAPI 保护的数据;然而,有时您可能不希望如此。通过让应用程序对生成密钥做出贡献,那么该密钥就变成了应用程序特定的,并且任何由该应用程序保护的数据,只有当他们知道熵时才能再次解除保护。

虽然生成主密钥,然后使用该主密钥生成其他密钥来执行实际加密可能看起来是一个冗长的方法,但它确实有一个重要优点。由于在用户密码保护的主密钥和用于保护数据的实际密钥之间存在额外的抽象层,这意味着当用户更改密码时,只需要重新保护主密钥;不需要重新保护任何受保护的数据。由于主密钥比数据小得多,因此可以节省大量性能。

当用户的密码更改时,自然会生成一个新的主密钥。然后使用该新的主密钥生成新的单独密钥。然而,由于所有先前生成的单独密钥都是从旧主密钥派生的,因此Windows需要存储所有以前的主密钥,它确实这样做了。Windows永远不会忘记主密钥,并且所有受保护的数据都带有一个GUID,指示用于保护数据的主密钥。因此,在适应性方面,DAPI能够应对用户密码的更改,同时确保a) 受保护的数据无需重新保护,b) 用于以前保护数据的密钥仍然可用,c) 它可以自动为您完成所有这些。

除非计算机是域的成员,否则DAPI只能在用于保护数据的同一台计算机上撤消保护。

除了允许用户级别保护(即主密钥基于用户密码,并且一个用户的受保护数据不能被另一个用户取消保护)外,DAPI还提供机器级别保护,即主密钥基于机器特定信息。机器级主密钥允许应用程序存储受保护的数据,以便所有应用程序用户都可以取消保护。已经描述的过程中唯一的区别是主密钥是从机器特定信息而不是用户特定信息生成的。


我认为从发布的文章中需要理解的关键点是:“大多数情况下,DAPI密钥管理过程是不可见的,通常您不需要担心它”。这就使我倾向于采用定制解决方案。足够好的保护并不总是足够好。 - Michael Brown
注意:截至2017年5月18日,文章链接返回404错误页面:http://www.dsmyth.net/wiki/Print.aspx?Page=StudyNotes_DAPI - iokevins

11

我曾经研究过它的代码,它使用Windows的advapi32来完成其“肮脏工作”。因此,密钥不存储在应用程序的内存中。

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static int SystemFunction040([In, Out] SafeBSTRHandle pDataIn, [In] uint cbDataIn, [In] uint dwFlag)

这被更广泛的称为 RtlEncryptMemory

它可以使用 RtlDecryptMemory (SystemFunction041) 进行解密。

我确定编译器也会对 SecurityCriticalAttribute 做一些处理。

编辑 这是使用4.0反映的。其他版本可能会有所不同。


9
正如其他人已经回答的那样,SecureString的内容使用DPAPI加密,因此密钥不存储在您的应用程序中,它们是操作系统的一部分。我不能百分之百确定,但我认为SecureString使用特定于用户的密钥,因此即使另一个进程访问了内存块,也必须在相同的凭据下运行才能简单地使用DPAPI解密内容。即使没有,机器密钥(理论上)也可以防止字符串在转移到另一台系统时被轻易解密。
更重要的是,使用SecureString的方式和时间。它应该用于存储需要在内存中保留“扩展”时间段的字符串数据,但这些数据不需要以其解密形式频繁使用。在某个时候,您将不得不将其解密为常规的System.String或System.Char[]。这是它在内存中最容易受到攻击的时候。如果您经常这样做,那么您就有多个解密后的字符串副本漂浮在内存中等待收集。
通常情况下,如果我正在读取需要保留以供不频繁使用的加密数据(例如PayPal或Amazon API交互)的登录凭据,则将这些凭据存储/缓存为SecureString,然后仅在需要时解密它足够长的时间进行web服务调用,并确保任何解密副本的寿命仅为几行代码。
使用关键块或类似功能提示CLR在使用解密字符串时不应切换上下文,以提高在缓存或交换内存之前收集任何解密副本的机会。

3

通过 DPAPI 魔法:

这个类使用数据保护 API(DPAPI)受保护的内存模型来存储其数据。换句话说,在 SecureString 中存储数据时,数据始终处于加密状态。 加密密钥由本地安全机构子系统(LSASS.EXE)管理,并且通过 DPAPI,可以通过进程间通信解密数据。


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