SecureString在C#应用程序中是否实用?

时间:2014-10-04 08:10:18

标签: c# security

如果我的假设在这里错了,请随意纠正我,但让我解释一下我为什么要问。

取自MSDN,SecureString

  

代表应保密的文字。文本在使用时加密以保护隐私,并在不再需要时从计算机内存中删除。

我明白了,在SecureString System.String上存储密码或其他私人信息是完全合理的,因为你可以控制它实际存储在内存中的方式和时间,因为System.String

  

既是不可变的,当不再需要时,不能以编程方式安排垃圾收集;也就是说,实例在创建后是只读的,并且无法预测实例何时从计算机内存中删除。因此,如果String对象包含敏感信息(如密码,信用卡号或个人数据),则使用该信息后可能会显示该信息,因为您的应用程序无法从计算机内存中删除数据。

但是,对于GUI应用程序(例如,ssh客户端),必须从 SecureString构建System.String 。所有文本控件都使用字符串作为其基础数据类型

因此,这意味着每次用户按下某个键时,就会丢弃那里的旧字符串,并构建一个新字符串来表示文本框内的值,即使使用密码掩码。 我们无法控制何时或是否从内存中丢弃任何值

现在是时候登录服务器了。你猜怎么着? 您需要在连接上传递字符串以进行身份​​验证。所以让我们将SecureString转换为System.String ....现在我们在堆上有一个字符串,无法强制它通过垃圾收集(或将0写入其缓冲区)。 / p>

我的观点:无论你做什么,在某个地方,SecureString 转换为System.String ,意味着它至少会在某个时刻存在于堆上(没有任何垃圾收集保证)。

我的观点不是:是否存在绕过将字符串发送到ssh连接或绕过控件存储字符串(制作自定义控件)的方法。对于这个问题,您可以将“ssh connection”替换为“登录表单”,“注册表单”,“付款表单”,“食物 - 你愿意喂你的小狗但不是你孩子的表格”,等

  • 那么,使用SecureString实际上变成了什么点 实用?
  • 是否值得花费额外的开发时间来彻底根除 使用System.String对象?
  • SecureString的重点是简单地减少System.String在堆上的时间(降低移动到物理交换文件的风险)?
  • 如果攻击者已经拥有进行堆检查的方法,那么他很可能(A)已经有了读取击键的方法,或者(B)已经实际拥有该机器 ...那么使用SecureString会阻止他获取数据吗?
  • 这只是“通过默默无闻的安全”吗?

对不起,如果我把问题放得太厚,好奇心就好了。随意回答我的任何或所有问题(或告诉我,我的假设是完全错误的)。 :)

8 个答案:

答案 0 :(得分:226)

SecureString实际上非常实用。

你知道我见过这种情景多少次了吗? (答案是:很多!):

  • 密码意外地出现在日志文件中。
  • 密码正在某处显示 - 一旦GUI显示正在运行的应用程序命令行,并且命令行由密码组成。的糟糕即可。
  • 使用内存分析器与您的同事分析软件。同事在内存中看到您的密码。听起来不真实吗?完全没有。
  • 我曾经使用RedGate软件在异常的情况下捕获局部变量的“值”,非常有用。虽然,我可以想象它会意外地记录“字符串密码”。
  • 包含字符串密码的崩溃转储。

你知道如何避免所有这些问题吗? SecureString。它通常会确保你不会犯这样的愚蠢错误。它是如何避免的呢?通过确保密码在非托管内存中加密,只有当您90%确定正在执行的操作时才能访问实际值。

从某种意义上说,SecureString非常容易实现:

1)所有内容都已加密

2)用户致电AppendChar

3)解密UNMANAGED MEMORY中的所有内容并添加角色

4)在UNMANAGED MEMORY中再次加密所有内容。

如果用户有权访问您的计算机,该怎么办?病毒是否能够访问所有SecureStrings?是。您需要做的就是在解密内存时将自己挂钩到RtlEncryptMemory,您将获得未加密的内存地址的位置,并将其读出。瞧!实际上,您可以制作一种病毒,不断扫描SecureString的使用情况并记录所有活动。我并不是说这将是一件容易的事,但可以做到。如您所见,一旦系统中存在用户/病毒,SecureString的“强大”就完全消失了。

你的帖子中有几点。当然,如果你在内部使用一些带有“字符串密码”的UI控件,那么使用实际的SecureString就不那么有用了。虽然,它仍然可以防止我上面列出的一些愚蠢。

另外,正如其他人所说,WPF支持PasswordBox,在SecurePassword property.

内部使用SecureString

底线是;如果您有敏感数据(密码,信用卡,......),请使用SecureString。这就是C#Framework所遵循的。例如,NetworkCredential类将密码存储为SecureString。如果你看一下this,你可以在SecureString的.NET框架中看到超过80种不同的用法。

在许多情况下,您必须将SecureString转换为字符串,因为某些API需要它。

通常的问题是:

  1. API是GENERIC。它不知道有敏感的数据。
  2. API知道它正在处理敏感数据并使用“字符串” - 这只是糟糕的设计。
  3. 您提出了一个好处:SecureString转换为string时会发生什么?这只能因为第一点而发生。例如。 API不知道它是敏感数据。我个人没有看到过这种情况。从SecureString中获取字符串并不是那么简单。

    由于一个简单的原因,这并不简单;它从来没有打算让用户将SecureString转换为字符串,正如你所说:GC会启动。如果你看到自己这样做,你需要退后一步问自己:为什么我甚至这样做,或者我真的需要这,为什么?

    我看到了一个有趣的案例。即,WinApi函数LogonUser将LPTSTR作为密码,这意味着您需要调用SecureStringToGlobalAllocUnicode。这基本上为您提供了未加密的密码,该密码位于非托管内存中。你需要在完成后立即摆脱它:

    // Marshal the SecureString to unmanaged memory.
    IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
    try
    {
       //...snip...
    }
    finally 
    {
       // Zero-out and free the unmanaged string reference.
       Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
    }
    

    您始终可以使用扩展方法扩展SecureString类,例如ToEncryptedString(__SERVER__PUBLIC_KEY),它会为您提供使用服务器公钥加密的string SecureString实例。只有服务器可以解密它。问题解决了:垃圾收集永远不会看到“原始”字符串,因为您从未在托管内存中公开它。这正是在PSRemotingCryptoHelperEncryptSecureStringCore(SecureString secureString))中完成的工作。

    与几乎相关的东西: Mono SecureString根本不加密。该实现已被注释掉,因为..等待它.. "It somehow causes nunit test breakage",这带来了我的最后一点:

    任何地方都不支持

    SecureString。如果平台/体系结构不支持SecureString,您将获得异常。文档中支持的平台列表。

答案 1 :(得分:15)

您假设中的问题很少。

首先,SecureString类没有String构造函数。为了创建一个,你分配一个对象,然后附加字符。

对于GUI或控制台,您可以非常轻松地将每个按下的键传递给安全字符串。

该类的设计方式是您不能错误地访问存储的值。这意味着您无法直接从中获取string密码。

因此,要使用它,例如,要通过Web进行身份验证,您必须使用同样安全的类。

在.NET框架中,您有一些可以使用SecureString的类

  • WPF的PasswordBox控件在内部将密码保存为SecureString。
  • System.Diagnostics.ProcessInfo' Password属性是SecureString。
  • X509Certificate2的构造函数使用SecureString作为密码。

(more)

总而言之,SecureString类可能很有用,但需要开发人员给予更多关注。

所有这些,以及示例,都在MSDN的SecureString

文档中有详细描述。

答案 2 :(得分:10)

如果出现以下情况,SecureString非常有用:

  • 您逐个字符构建(例如从控制台输入)或从非托管API获取

  • 您可以通过将其传递给非托管API(SecureStringToBSTR)来使用它。

如果您将其转换为托管字符串,那么您已经失去了它的目的。

更新以回应评论

  

...或者像你提到的BSTR,似乎没有任何更安全的

在转换为BSTR之后,使用BSTR的非托管组件可以将内存归零。非托管内存在某种意义上更安全,可以通过这种方式重置。

但是,.NET Framework中很少有支持SecureString的API,所以你说它今天的价值非常有限。

我将看到的主要用例是在客户端应用程序中,要求用户输入高度敏感的代码或密码。可以逐个字符地使用用户输入来构建SecureString,然后可以将其传递给非托管API,该API在使用它之后将其接收的BSTR归零。任何后续内存转储都不包含敏感字符串。

在服务器应用程序中,很难看出它的用处。

更新2

接受SecureString的.NET API的一个示例是this constructor for the X509Certificate class。如果您使用ILSpy或类似的,您会看到SecureString在内部转换为非托管缓冲区(Marshal.SecureStringToGlobalAllocUnicode),然后在完成(Marshal.ZeroFreeGlobalAllocUnicode)时归零。

答案 3 :(得分:4)

正如您已正确识别的那样,SecureString提供了一个优于string的特定优势:确定性删除。这个事实存在两个问题:

  1. 正如其他人所提到的那样,正如你自己所注意到的,这本身并不足够。您必须确保流程的每个步骤(包括输入的检索,字符串的构造,使用,删除,运输等)都不会违反使用SecureString的目的。这意味着您必须小心不要创建GC管理的不可变string或任何其他存储敏感信息的缓冲区(或者您还必须跟踪 )。实际上,这并不总是很容易实现,因为许多API只提供了使用string而不是SecureString的方法。即使你确实做对了一切......
  2. SecureString可以防止非常特殊的攻击(对于其中一些攻击,它甚至不可靠)。例如,SecureString允许您缩短攻击者可以转储进程内存并成功提取敏感信息的时间窗口(再次,正如您正确指出的那样),但希望窗口也是如此对于攻击者来说很小的内存快照根本不算安全。
  3. 那么,你何时应该使用它?只有当你正在处理一些可以让你与SecureString一起工作以满足你所有需求的东西时,你仍然应该注意这只在特定情况下是安全的。

答案 4 :(得分:3)

我想谈谈这一点:

  

如果攻击者已经拥有进行堆检查的方法,那么他们很可能(A)已经有了读取键击的方法,或者(B)已经实际拥有该机器 ...那么使用SecureString会阻止他们获取数据吗?

攻击者可能无法完全访问计算机和应用程序,但可以通过其方式访问进程内存的某些部分。当特殊构造的输入可能导致应用程序暴露或覆盖内存的某些部分时,通常会由缓冲区溢出等错误引起。

HeartBleed泄漏记忆

以Heartbleed为例。特殊构造的请求可能导致代码将进程内存的随机部分暴露给攻击者。攻击者可以从内存中提取SSL证书,但他唯一需要的就是使用格式错误的请求。

在托管代码的世界中,缓冲区溢出变得越来越不常成为问题。在WinForms的情况下,数据已经以不安全的方式存储,您无法对其进行任何操作。这使SecureString的保护变得毫无用处。

但是,GUI 可以编程为使用SecureString,在这种情况下,减少内存中密码可用性的窗口是值得的。例如,WPF中的PasswordBox.SecurePassword类型为SecureString

答案 5 :(得分:2)

以下文本是从HP Fortify静态代码分析器

复制的

<强>摘要: PassGenerator.cs中的方法PassString()以不安全的方式(即以字符串形式)存储敏感数据,从而可以提取 通过检查堆来获取数据。

<强>说明: 存储在内存中的敏感数据(如密码,社会安全号码,信用卡号等)可能会泄露 存储在托管String对象中。字符串对象没有固定,因此垃圾收集器可以随意重新定位这些对象 在内存中保留几份副本。默认情况下,这些对象不加密,因此任何能够读取进程内存的人都将是 能够看到内容。此外,如果进程的内存被换出到磁盘,则字符串的未加密内容 将被写入交换文件。最后,由于String对象是不可变的,因此只能从内存中删除String的值 由CLR垃圾收集器完成。除非CLR内存不足,否则不需要运行垃圾收集器,因此存在 无法保证何时进行垃圾收集。在应用程序崩溃的情况下,内存转储 应用程序可能会泄露敏感数据。

<强>建议: 不是将敏感数据存储在Strings等对象中,而是将它们存储在SecureString对象中。每个对象将其内容存储在一个 加密格式始终在内存中。

答案 6 :(得分:0)

前一段时间,我不得不针对Java信用卡支付网关创建一个c#接口,并且需要一个兼容的安全通信密钥加密。由于Java实现非常具体,因此我必须以给定的方式处理受保护的数据。

我发现此设计比使用SecureString更加易于使用和简便……对于那些喜欢使用的人……可以自由使用,没有法律限制:-)。请注意,这些类是内部类,您可能需要将它们公开。

B

答案 7 :(得分:0)

Microsoft不建议将SecureString用于较新的代码。

摘自SecureString Class的文档:

  

重要

     

我们不建议您将SecureString类用于新   发展。有关更多信息,请参见SecureString shouldn't be used

建议:

  

请勿将SecureString用于新代码。将代码移植到.NET Core时,   考虑到数组的内容未在内存中加密。

     

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