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

取自MSDN,一个SecureString:

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

我明白了,在系统上的SecureString中存储密码或其他私人信息是完全有意义的。字符串,因为您可以控制它实际存储在内存中的方式和时间,因为System。字符串:

is both immutable and, when no longer needed, cannot be programmatically scheduled for garbage collection; that is, the instance is read-only after it is created and it is not possible to predict when the instance will be deleted from computer memory. Consequently, if a String object contains sensitive information such as a password, credit card number, or personal data, there is a risk the information could be revealed after it is used because your application cannot delete the data from computer memory.

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

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

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

我的观点是:无论你做什么,在某个地方,那个SecureString会被转换成一个系统。字符串,这意味着它至少在某个时候存在于堆上(没有任何垃圾收集的保证)。

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

So, at what point does using a SecureString actually become practical? Is it ever worth the extra development time to completely eradicate the use of a System.String object? Is the whole point of SecureString to simply reduce the amount of time a System.String is on the heap (reducing its risk of moving to a physical swap file)? If an attacker already has the means for a heap inspection, then he most likely either (A) already has the means to read keystrokes, or (B) already physically has the machine... So would using a SecureString prevent him from getting to the data anyways? Is this just "security through obscurity"?

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


你的假设有几个问题。

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

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

该类的设计方式使您无法访问所存储的值。这意味着您不能直接从该字符串获取作为密码的字符串。

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

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

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

(更多)

综上所述,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)完成时将其归零。


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

As others have mentioned and as you have noticed by yourself, this isn't enough by itself. You have to make sure that every step of the process (including retrieval of input, construction of the string, usage, deletion, transportation, etc) happens without defeating the purpose of using SecureString. This means that you must be careful to never create a GC-managed immutable string or any other buffer that will store the sensitive information (or you'll have to keep track of that as well). In practice, this isn't always easy to achieve, because lots of APIs only offer a way to work with string, not SecureString. And even if you do manage to everything right... SecureString protects against very specific kinds of attack (and for some of them, it's not even that reliable). For example, SecureString does allow you to shrink the time window in which an attacker can dump the memory of your process and successfully extract the sensitive information (again, as you correctly pointed out), but hoping that the window is too small for the attacker to take a snapshot of your memory isn't considered security at all.

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


我想谈谈这一点:

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

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

HeartBleed内存泄漏

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

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

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


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

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

A password appears in a log file accidentally. A password is being shown at somewhere - once a GUI did show a command line of application that was being run, and the command line consisted of password. Oops. Using memory profiler to profile software with your colleague. Colleague sees your password in memory. Sounds unreal? Not at all. I once used RedGate software that could capture the "value" of local variables in case of exceptions, amazingly useful. Though, I can imagine that it will log "string passwords" accidentally. A crash dump that includes string password.

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

从某种意义上说,SecureString工作起来很容易:

1)一切都是加密的

2)用户调用AppendChar

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

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

What if the user has access to your computer? Would a virus be able to get access to all the SecureStrings? Yes. All you need to do is hook yourself into RtlEncryptMemory when the memory is being decrypted, you will get the location of the unencrypted memory address, and read it out. Voila! In fact, you could make a virus that will constantly scan for usage of SecureString and log all the activities with it. I am not saying it will be an easy task, but it can be done. As you can see, the "powerfulness" of SecureString is completely gone once there's a user/virus in your system.

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

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

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

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

通常的问题是:

API是GENERIC。它不知道那里有敏感数据。 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),它为您提供了使用服务器公钥加密的SecureString的字符串实例。只有服务器才能解密它。问题解决了:垃圾收集将永远不会看到“原始”字符串,因为您从未在托管内存中公开它。这正是在PSRemotingCryptoHelper (EncryptSecureStringCore(SecureString SecureString))中所做的。

与此非常相关的是:Mono SecureString根本不加密。该实现已被注释掉,因为..等等…“它在某种程度上导致nunit测试中断”,这就引出了我的最后一点:

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


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

文摘: PassGenerator.cs中的PassString()方法以不安全的方式存储敏感数据(即以字符串形式存储),使得提取敏感数据成为可能 通过检查堆来获取数据。

Explanation: Sensitive data (such as passwords, social security numbers, credit card numbers etc.) stored in memory can be leaked if it is stored in a managed String object. String objects are not pinned, so the garbage collector can relocate these objects at will and leave several copies in memory. These objects are not encrypted by default, so anyone who can read the process' memory will be able to see the contents. Furthermore, if the process' memory gets swapped out to disk, the unencrypted contents of the string will be written to a swap file. Lastly, since String objects are immutable, removing the value of a String from memory can only be done by the CLR garbage collector. The garbage collector is not required to run unless the CLR is low on memory, so there is no guarantee as to when garbage collection will take place. In the event of an application crash, a memory dump of the application might reveal sensitive data.

建议: 将敏感数据存储在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身份验证。