SecureString在C#应用程序中实用吗?

SecureString在C#应用程序中实用吗?,c#,security,C#,Security,如果我的假设是错误的,请随时纠正我,但让我解释一下为什么我会这样问 取自MSDN,一个SecureString: 表示应保密的文本。文本在使用时为了隐私而加密,在不再需要时从计算机内存中删除 我明白了,通过System.String,将密码或其他私人信息存储在SecureString中是完全有意义的,因为您可以控制它实际存储在内存中的方式和时间,因为System.String: 是不可变的,并且当不再需要时,不能以编程方式安排垃圾收集;也就是说,实例创建后是只读的,无法预测何时从计算机内存中删除

如果我的假设是错误的,请随时纠正我,但让我解释一下为什么我会这样问

取自MSDN,一个
SecureString

表示应保密的文本。文本在使用时为了隐私而加密,在不再需要时从计算机内存中删除

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

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

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

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

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

我的观点是:不管你做什么,在这条路线上的某个地方,
SecureString
将被转换成一个
System.String
,这意味着它至少会在某个点存在于堆上(不保证垃圾收集)

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

  • 那么,在什么情况下使用
    SecureString
    才是真正的问题呢 实用的
  • 是否值得花费额外的开发时间来彻底根除病毒
    System.String
    对象的使用
  • SecureString
    的全部目的是为了减少
    系统.String
    在堆上的时间(降低其移动到物理交换文件的风险)
  • 如果攻击者已经有了堆检查的方法,那么他很可能(a)已经有了读取击键的方法,或者(B)已经实际拥有了机器。。。那么,使用
    SecureString
    会阻止他以任何方式获取数据吗
  • 这仅仅是“默默无闻的安全”吗

对不起,如果我把问题说得太过分了,好奇心就占了上风。请随意回答我的任何或所有问题(或告诉我我的假设完全错误):)

在您的假设中,这些问题很少

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

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

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

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

在.NET framework中,有几个类可以使用SecureString

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

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


所有这些都在MSDN的文档中有很好的描述,并附有示例。如果满足以下条件,SecureString将非常有用:

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

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

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

更新以回应评论

。。。或者像你提到的BSTR,看起来不太安全

将其转换为BSTR后,使用BSTR的非托管组件可以将内存归零。非托管内存更安全,因为它可以通过这种方式重置

然而,.NET Framework中支持SecureString的API非常少,因此您可以说它在今天的价值非常有限

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

在服务器应用程序中,很难看到它将在何处使用
// 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);
}
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();
        }
    }
}