SecureString在C#应用程序中是否实用?
如果我的假设在这里是错误的,请随时纠正我,但让我解释我为什么问。
采取从MSDN,一个SecureString
:
代表应该保密的文字。 文本在使用时被隐私encryption,不再需要时从计算机内存中删除。
我得到这个,通过System.String
存储密码或其他私人信息在SecureString
是完全有意义的,因为你可以控制它是如何以及何时被存储在内存中的,因为System.String
:
是不可变的,当不再需要时,不能以编程方式安排垃圾收集; 也就是说,实例在创build后是只读的,并且不可能预测实例何时从计算机内存中删除。 因此,如果String对象包含密码,信用卡号码或个人数据等敏感信息,则在使用该信息之后可能会泄露信息,因为您的应用程序无法从计算机内存中删除数据。
但是,对于GUI应用程序(例如,ssh客户端), 必须从 System.String
构build SecureString
。 所有的文本控件都使用string作为其基础数据types 。
所以,这意味着每当用户按下某个键时,即使使用密码掩码,旧的string也会被丢弃,并build立一个新的string来表示文本框内的值。 而且我们无法控制这些值何时或是否从内存中被丢弃 。
现在是login到服务器的时候了。 你猜怎么了? 您需要通过连接传递string进行身份validation 。 所以让我们把我们的SecureString
转换成一个System.String
….现在我们在堆上有一个string,没有办法强制它通过垃圾回收(或写0到它的缓冲区)。
我的观点是 :不pipe你做什么,在某个地方, SecureString
将被转换成一个System.String
,这意味着它至less会在堆上存在(没有任何垃圾收集的保证)。
我的观点并不是 :是否有办法避免发送一个string到一个SSH连接,或者绕过一个控制存储一个string(做一个自定义控件)。 对于这个问题,你可以用“login表单”,“registry单”,“付款表单”,“食品 – 你会喂小孩但不是你的孩子的forms”代替“ssh连接”。等等
- 那么,在什么时候使用
SecureString
实际上变得实用? - 是否值得花费额外的开发时间来彻底根除
System.String
对象的使用? - 整个
SecureString
是简单地减less一个System.String
在堆上的时间(减less移动到物理交换文件的风险)? - 如果攻击者已经有了堆检查的手段,那么他很有可能(A)已经有了读击键的方法,或者(B)已经物理地拥有了机器 …所以使用
SecureString
防止他进入数据呢? - 这只是“通过默默无闻的安全”?
对不起,如果我把问题放在太厚的地方,好奇心就会越来越好。 随时回答我的任何或所有问题(或告诉我,我的假设是完全错误的)。 🙂
实际上SecureString
是非常实用的。
你知道有多less次我见过这样的场景(答案是:很多!):
- 密码意外出现在日志文件中。
- 在某处显示密码 – 一旦GUI显示正在运行的应用程序的命令行,并且命令行由密码组成。 哎呀
- 使用内存分析器与您的同事分析软件。 同事在内存中看到你的密码。 听起来不真实? 一点也不。
- 我曾经使用
RedGate
软件捕捉局部variables的“值”,以防exception,非常有用。 虽然,我可以想象它会不小心logging“string密码”。 - 包含string密码的故障转储。
你知道如何避免所有这些问题吗? SecureString
。 它通常确保你不会犯这样的愚蠢错误。 它如何避免它? 通过确保密码在非托pipe内存中被encryption,只有当你90%确定你在做什么时,才能访问真实值。
从这个意义上说, SecureString
工作非常简单:
1)一切都被encryption
2)用户调用AppendChar
3)解密UNMANAGED MEMORY中的所有内容并添加字符
4)在UNMANAGED MEMORY中再次encryption所有内容。
如果用户有权访问您的计算机? 病毒能够访问所有的SecureStrings
吗? 是。 所有你需要做的就是在内存被解密的时候把自己挂到RtlEncryptMemory
,你将得到未encryption的内存地址的位置并读出来。 瞧! 事实上,你可能会造成一种病毒,不断扫描SecureString
使用情况,并logging所有的活动。 我不是说这将是一件容易的事,但是可以做到。 正如您所看到的,一旦系统中存在用户/病毒, SecureString
的“强大function”就完全消失了。
你的post中有几点。 当然,如果你使用一些内部持有“string密码”的UI控件,使用实际的SecureString
并不是那么有用。 但是,它仍然可以防止我上面列出的一些愚蠢。
另外,正如其他人所指出的,WPF支持在内部使用SecureString
PasswordBox。
底线是; 如果您有敏感数据(密码,信用卡等),请使用SecureString
。 这就是C#框架所遵循的。 例如, NetworkCredential
类将密码存储为SecureString
。 如果你看这个 ,你可以在SecureString
.NET框架中看到80多种不同的用法。
有很多情况下,您必须将SecureString
转换为string,因为有些API期望它。
通常的问题是:
- api是GENERIC。 它不知道有一个敏感的数据。
- api知道它处理敏感数据并使用“string” – 这只是糟糕的devise。
你提出了很好的观点:当SecureString
被转换为string
时会发生什么? 这只能由于第一点而发生。 例如API不知道它是敏感数据。 我个人没有看到发生。 从SecureString中获取string并不那么简单。
这个原因并不简单, 就像你所说的那样,从来没有打算让用户把SecureString转换成string,GC会join进来。如果你看到自己这样做了,你需要退后一步问自己:为什么我要做这个,或者我真的需要这个,为什么?
我看到一个有趣的案例。 即,WinApi函数LogonUser将LPTSTR作为密码,这意味着您需要调用SecureStringToGlobalAllocUnicode
。 这基本上给你未encryption的密码,生活在非托pipe内存。 你需要尽快摆脱这一切。
您始终可以使用扩展方法(如ToEncryptedString(__SERVER__PUBLIC_KEY)
来扩展SecureString
类,该扩展方法为您提供使用服务器公钥encryption的SecureString
string
实例。 只有服务器可以解密它。 问题解决了,GC永远不会看到“原始”string,因为你永远不会在托pipe内存中显示它。 这正是在PSRemotingCryptoHelper
( EncryptSecureStringCore(SecureString secureString)
)中EncryptSecureStringCore(SecureString secureString)
。
和几乎相关的东西: Mono SecureString根本不encryption 。 实施已被注释掉,因为..等待它.. “它以某种方式导致nunittesting破损” ,这引起了我的最后一点:
SecureString
在任何地方都不受支持。 如果平台/体系结构不支持SecureString
,则会得到exception。 文档中有一个支持的平台列表。
我最长的StackOverflow后,任何人可以计数我重复SecureString
将得到+1 :-)。
在你的假设是less数问题。
首先,SecureString类没有String构造函数。 为了创build一个你分配一个对象,然后附加字符。
在GUI或控制台的情况下,您可以非常轻松地将每个按下的按键传递给安全的string。
这个类的devise方式是你不能错误地访问存储的值。 这意味着你不能直接从它获取string
作为密码。
因此,为了使用它,例如,通过networking进行身份validation,您将不得不使用适当的类也是安全的。
在.NET框架中,您有几个可以使用SecureString的类
- WPF的PasswordBox控件将密码保存为内部的SecureString。
- System.Diagnostics.ProcessInfo的Password属性是一个SecureString。
- X509Certificate2的构造函数使用SecureString作为密码。
(更多)
总而言之,SecureString类可能是有用的,但需要开发者更多的关注。
所有这些,都带有示例,在MSDN的SecureString文档中有很好的描述
一个SecureString是有用的,如果:
-
你可以通过字符(例如从控制台input)来构build它,或者从非托pipeAPI中获取它
-
通过将它传递给非托pipeAPI(SecureStringToBSTR)来使用它。
如果你把它转换成一个托pipestring,你已经打败了它的目的。
更新回应评论
…或者像你提到的BSTR,这似乎没有更安全的
在转换为BSTR后,使用BSTR的非托pipe组件可以清零内存。 非托pipe内存在这种意义上可以被重置的意义上更安全。
但是,.NET框架中支持SecureString的API非常less,所以您今天说的价值非常有限。
我将看到的主要用例是在一个客户端应用程序中,要求用户input一个高度敏感的代码或密码。 用户input可以逐个字符地构build一个SecureString,然后这个可以传递给一个非托pipe的API,这个API在使用它之后将它接收到的BSTR清零。 任何后续内存转储将不包含敏感string。
在服务器应用程序中,很难看到它在哪里有用。
更新2
接受SecureString的.NET API的一个示例是X509Certificate类的此构造函数 。 如果你用ILSpy或类似的东西进行拼写,你会发现SecureString在内部被转换成一个非托pipe缓冲区( Marshal.SecureStringToGlobalAllocUnicode
),然后在完成( Marshal.ZeroFreeGlobalAllocUnicode
)之后将其归零。
正如您已经正确识别, SecureString
提供了一个string
特定优势:确定性删除。 这个事实有两个问题:
- 正如其他人所提到的那样,正如你自己已经注意到的那样,这本身还不够。 您必须确保每个步骤(包括input的检索,string的构build,使用,删除,运输等)的发生都不会影响使用
SecureString
的目的。 这意味着你必须小心,永远不要创build一个GCpipe理的不可变string
或任何其他缓冲区来存储敏感信息(或者你也必须跟踪这些信息 )。 在实践中,这并不总是很容易实现,因为许多API只提供了一种使用string
而不是SecureString
。 即使你确实做到了一切 -
SecureString
可以抵御非常特殊的攻击(对于其中一些攻击来说,它甚至不可靠)。 例如,SecureString
允许您缩小攻击者可以转储进程内存的时间窗口,并成功提取敏感信息(正如您正确指出的那样),但希望窗口对于攻击者来说太小拍摄你的记忆快照根本不被视为安全。
那么,你应该什么时候使用它? 只有当你正在处理一些可以让你使用SecureString
满足你所有需求的东西时,你仍然应该注意,只有在特定的情况下它才是安全的。
我想谈谈这一点:
如果攻击者已经有了堆检查的手段,那么他们很可能(A)已经有了读取击键的方法,或者(B)已经物理地拥有了机器 …所以使用
SecureString
阻止他们进入数据呢?
攻击者可能无法完全访问计算机和应用程序,但可以访问进程内存的某些部分。 它通常是由缓冲区溢出等错误引起的,特殊构build的input会导致应用程序暴露或覆盖部分内存。
HeartBleed泄漏的内存
以Heartbleed为例。 特殊构build的请求可能会导致代码将进程内存的随机部分暴露给攻击者。 攻击者可以从内存中提取SSL证书,但他只需要使用格式错误的请求。
在托pipe代码的世界里,缓冲区溢出成为一个问题不大常见。 而在WinForms的情况下,数据已经以不安全的方式存储,你不能做任何事情。 这使得SecureString
的保护几乎没用。
但是, 可以将GUI编程为使用SecureString
,在这种情况下,减less内存中密码可用性的窗口可能是值得的。 例如,来自WPF的PasswordBox.SecurePassword是SecureString
types。
下面的文字是从HP Fortify静态代码分析器复制的
摘要: PassGenerator.cs中的PassString()方法以不安全的方式(即string)存储敏感数据,从而可以通过检查堆来提取数据。
说明:存储在内存中的敏感数据(如密码,社会安全号码,信用卡号码等)如果存储在受pipe理的string对象中,则可能会泄漏。 string对象不固定,所以垃圾收集器可以随意重新定位这些对象,并在内存中留下几个副本。 这些对象默认是不encryption的,所以任何可以读取进程内存的人都可以看到内容。 此外,如果进程的内存被换出到磁盘,string的未encryption内容将被写入交换文件。 最后,由于String对象是不可变的,因此只能通过CLR垃圾收集器来从内存中删除String的值。 垃圾收集器不需要运行,除非CLR内存不足,所以不能保证什么时候垃圾回收会发生。 在应用程序崩溃的情况下,应用程序的内存转储可能会显示敏感数据。
build议:不要将敏感数据存储在string等对象中,而是将它们存储在SecureString对象中。 每个对象始终将其内容以encryption格式存储在内存中。