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

取自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();
        }
    }
}

其他回答

我想谈谈这一点:

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

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

HeartBleed内存泄漏

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

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

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

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

来自SecureString类的文档:

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

建议:

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

前段时间,我必须为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();
        }
    }
}

以下文本是从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等对象中。对象中存储其内容 在内存中始终保存加密格式。

正如您已经正确识别的那样,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满足所有需求的东西时,即使这样,您仍然应该注意,这仅在特定情况下是安全的。