SecureString 在 C# 应用中实用吗?

如果我的假设是错误的,请随时纠正我,但让我解释为什么我问。

取自 MSDN, SecureString:

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

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

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

然而,在GUI应用程序(例如,ssh客户端)的情况下,SecureString 必须从一个 System.String。所有的文本都控制使用字符串作为其底层数据类型

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

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

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

我的观点不是:是否有方法避免将字符串发送到ssh连接,或避免让控件存储字符串(创建自定义控件)。对于这个问题,您可以将“ssh连接”替换为“登录表单”、“注册表单”、“支付表单”、“你要喂你的小狗但不是你的孩子的食物表单”等等。

    那么,在什么情况下使用SecureString实际上会变成 实用?李< / > 是否值得花费额外的开发时间来彻底根除 System.String对象的使用?李< / >
  • SecureString的全部意义是否仅仅是减少System.String在堆上的时间(减少其移动到物理交换文件的风险)?
  • 如果攻击者已经有了堆检查的方法,那么他很可能要么(a)已经有了读取击键的方法,要么(B)已经物理上有机器…那么使用SecureString会阻止他获取数据吗?
  • 这仅仅是“通过模糊获得安全”吗?

如果我的问题问得太过分了,对不起,我只是被好奇心控制了。请随意回答我的任何或所有问题(或者告诉我我的假设是完全错误的)。:)

55789 次浏览

你的假设有几个问题。

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

在GUI或控制台的情况下,您可以很容易地将每个按下的键传递给一个安全字符串。

该类的设计方式使您无法访问所存储的值。这意味着你不能直接从它那里获得string作为密码。

因此,为了使用它,例如,通过web进行身份验证,您将必须使用适当的类,这些类也是安全的。

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

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

141393年< a href = " https://stackoverflow.com/questions/141203/when-would-i-need-a-securestring-in-net/141393 " >(更多)< / >

综上所述,SecureString类可能很有用,但需要开发人员多加注意。

所有这些都在MSDN的SecureString文档中有很好的描述

SecureString在以下情况下是有用的:

  • 你可以一个字符一个字符地构建它(例如从控制台输入),或者从非托管API获取它

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

如果您曾经将它转换为托管字符串,那么您就破坏了它的目的。

更新在回应评论

... 或者像你提到的BSTR,这似乎并不安全

在它被转换为BSTR之后,消耗BSTR的非托管组件可以将内存归零。非托管内存更安全,因为它可以以这种方式重置。

然而,在. net框架中支持SecureString的api非常少,所以你说它现在的价值非常有限是对的。

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

在服务器应用程序中,很难看出它在什么地方有用。

更新2

接受SecureString的. net API的一个例子是这个构造函数用于X509Certificate类。如果你用ILSpy或类似的方法进行深入研究,你会看到SecureString在内部被转换为一个非托管缓冲区(Marshal.SecureStringToGlobalAllocUnicode),然后在用(Marshal.ZeroFreeGlobalAllocUnicode)完成时将其归零。

正如你已经正确识别的那样,SecureStringstring提供了一个特定的优势:确定性擦除。这一事实有两个问题:

  1. 正如其他人提到的,你自己也注意到了,光有这一点是不够的。你必须确保过程的每一步(包括检索输入、构造字符串、使用、删除、传输等)都不会违背使用SecureString的目的。这意味着你必须小心,永远不要创建gc管理的不可变string或任何其他存储敏感信息的缓冲区(否则你也必须跟踪)。在实践中,这并不总是容易实现,因为许多api只提供了一种使用string的方法,而不是SecureString。即使你把每件事都做好了……
  2. SecureString可以防止非常特定的攻击(对于其中一些攻击,它甚至不是那么可靠)。例如,SecureString确实允许你缩小攻击者可以转储进程内存并成功提取敏感信息的时间窗口(再次,正如你正确指出的那样),但是希望窗口太小以至于攻击者无法对你的内存进行快照根本不被认为是安全的。

那么,什么时候应该使用它呢?只有当你正在处理一些可以让你使用SecureString来满足你所有需求的东西时,即使这样,你仍然应该注意,这只在特定的情况下是安全的。

我想谈谈这一点:

如果攻击者已经有了堆检查的方法,那么他们很可能要么(a)已经有了读取击键的方法,要么(B)已经物理上拥有机器…那么使用SecureString会阻止他们获取数据吗?

攻击者可能没有对计算机和应用程序的完全访问权,但可以有方法访问进程内存的某些部分。当特殊构造的输入可能导致应用程序公开或覆盖某些内存时,这通常是由缓冲区溢出之类的错误引起的。

HeartBleed内存泄漏

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

在托管代码的世界中,缓冲区溢出成为一个不太常见的问题。在WinForms的情况下,数据已经以不安全的方式存储,您无法对此做任何事情。这使得使用SecureString的保护几乎毫无用处。

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

SecureString实际上有非常实际的用途。

你知道这样的场景我见过多少次吗?(答案是:很多!)

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

你知道如何避免所有这些问题吗?SecureString。它通常会确保你不会犯愚蠢的错误。如何避免呢?通过确保密码在非托管内存中加密,并且只有当您90%确定自己在做什么时才能访问真正的值。

在这种意义上,SecureString工作起来相当容易:

1)一切都是加密的

2)用户调用AppendChar

3)解密非托管内存中的所有内容并添加字符

4)在非托管内存中再次加密所有内容。

如果用户可以访问您的计算机怎么办?病毒能够访问所有的SecureStrings吗?是的。你所需要做的就是在内存被解密时将自己绑定到RtlEncryptMemory,你将获得未加密内存地址的位置,并将其读出。瞧!事实上,你可以制作一个病毒,它会不断地扫描SecureString的使用情况,并记录所有与之相关的活动。我并不是说这将是一项容易的任务,但它是可以做到的。正如你所看到的,一旦你的系统中有一个用户/病毒,SecureString的“强大”就完全消失了。

你的帖子里有几点。当然,如果你在内部使用一些包含“字符串密码”的UI控件,使用实际的SecureString并不是那么有用。尽管如此,它仍然可以防止我上面列出的一些愚蠢行为。

另外,正如其他人所注意到的,WPF支持PasswordBox,它通过< >强SecurePassword < / >强属性。在内部使用SecureString

底线是;如果你有敏感数据(密码、信用卡等),请使用SecureString。这就是c#框架所遵循的。例如,NetworkCredential类将密码存储为SecureString。如果你看一下,你会发现。net框架中SecureString有80多种不同的用法。

在很多情况下,你必须将SecureString转换为字符串,因为一些API需要它。

通常的问题是:

  1. API是GENERIC。它不知道那里有敏感数据。
  2. API知道它正在处理敏感数据并使用“字符串”——这只是糟糕的设计。

你提出了一个很好的观点:当SecureString转换为string时会发生什么?这只能因为第一点而发生。例如,API不知道它是敏感数据。我个人还没有看到这种情况发生。从SecureString中获取字符串并不是那么简单。

事情并不简单,原因很简单;它从来没有打算让用户将SecureString转换为string,正如你所说的:GC将会启动。如果你看到自己在这么做,你需要退一步问问自己:我为什么要这样做,或者我真的需要这样做吗?为什么?

我看到过一个有趣的例子。也就是说,WinApi函数LogonUser将lpstr作为密码,这意味着你需要调用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),它会给你一个使用服务器公钥加密的SecureStringstring实例。只有服务器才能解密它。问题解决了:垃圾收集将永远不会看到“原始”字符串,因为您从未在托管内存中公开它。这正是在PSRemotingCryptoHelper (EncryptSecureStringCore(SecureString secureString))中所做的。

和一些非常相关的东西:Mono SecureString根本不加密。该实现已被注释掉,因为..等等…"它以某种方式导致nunit测试中断",这就引出了我的最后一点:

并非所有地方都支持SecureString。如果平台/架构不支持SecureString,则会出现异常。文档中有一个支持的平台列表。

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

< >强文摘: PassGenerator.cs中的PassString()方法以不安全的方式存储敏感数据(即以字符串形式存储),使得提取敏感数据成为可能

.

.

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

.应用程序可能泄露敏感数据

< >强建议: 将敏感数据存储在SecureString对象中,而不是存储在string等对象中。对象中存储其内容

.内存中始终存在加密格式

前段时间,我必须为java信用卡支付网关创建一个c#接口,其中需要一个兼容的安全通信密钥加密。由于Java实现相当特定,我必须以特定的方式处理受保护的数据。

我发现这个设计非常容易使用,比使用SecureString更容易…对于那些喜欢使用…感觉自由,没有法律限制:-)。注意,这些类是内部的,您可能需要将它们设为公共的。

namespace Cardinity.Infrastructure
{
using System.Security.Cryptography;
using System;
enum EncryptionMethods
{
None=0,
HMACSHA1,
HMACSHA256,
HMACSHA384,
HMACSHA512,
HMACMD5
}




internal class Protected
{
private  Byte[] salt = Guid.NewGuid().ToByteArray();


protected byte[] Protect(byte[] data)
{
try
{
return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
}
catch (CryptographicException)//no reason for hackers to know it failed
{
#if DEBUG
throw;
#else
return null;
#endif
}
}


protected byte[] Unprotect(byte[] data)
{
try
{
return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
}
catch (CryptographicException)//no reason for hackers to know it failed
{
#if DEBUG
throw;
#else
return null;
#endif
}
}
}




internal class SecretKeySpec:Protected,IDisposable
{
readonly EncryptionMethods _method;


private byte[] _secretKey;
public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
{
_secretKey = Protect(secretKey);
_method = encryptionMethod;
}


public EncryptionMethods Method => _method;
public byte[] SecretKey => Unprotect( _secretKey);


public void Dispose()
{
if (_secretKey == null)
return;
//overwrite array memory
for (int i = 0; i < _secretKey.Length; i++)
{
_secretKey[i] = 0;
}


//set-null
_secretKey = null;
}
~SecretKeySpec()
{
Dispose();
}
}


internal class Mac : Protected,IDisposable
{
byte[] rawHmac;
HMAC mac;
public Mac(SecretKeySpec key, string data)
{


switch (key.Method)
{
case EncryptionMethods.HMACMD5:
mac = new HMACMD5(key.SecretKey);
break;
case EncryptionMethods.HMACSHA512:
mac = new HMACSHA512(key.SecretKey);
break;
case EncryptionMethods.HMACSHA384:
mac = new HMACSHA384(key.SecretKey);
break;
case EncryptionMethods.HMACSHA256:
mac = new HMACSHA256(key.SecretKey);


break;
case EncryptionMethods.HMACSHA1:
mac = new HMACSHA1(key.SecretKey);
break;


default:
throw new NotSupportedException("not supported HMAC");
}
rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));


}


public string AsBase64()
{
return System.Convert.ToBase64String(Unprotect(rawHmac));
}


public void Dispose()
{
if (rawHmac != null)
{
//overwrite memory address
for (int i = 0; i < rawHmac.Length; i++)
{
rawHmac[i] = 0;
}


//release memory now
rawHmac = null;


}
mac?.Dispose();
mac = null;


}
~Mac()
{
Dispose();
}
}
}

微软不建议对更新的代码使用SecureString

来自SecureString类的文档:

重要的

我们不建议你使用SecureString类的new 发展。有关更多信息,请参见SecureString不能被使用

建议:

新代码不使用SecureString。当将代码移植到。net Core时, 假设数组的内容在内存中没有加密 处理凭证的一般方法是避免使用和 相反,依靠其他方法进行身份验证,如证书或 Windows身份验证。< / p >