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

取自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"?

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


当前回答

前段时间,我必须为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类没有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)完成时将其归零。

前段时间,我必须为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身份验证。

我想谈谈这一点:

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

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

HeartBleed内存泄漏

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

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

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