C# - 比较两个SecureStrings是否相等

Sar*_*els 35 c# passwords equals securestring passwordbox

我有一个带有两个PasswordBox的WPF应用程序,一个用于密码,另一个用于第二次输入密码以进行确认.我想用来PasswordBox.SecurePassword获取SecureString密码,但我需要能够比较两个PasswordBoxes的内容以确保在我接受密码之前的相等性.但是,两个相同的SecureStrings不相同:

var secString1 = new SecureString();
var secString2 = new SecureString();
foreach (char c in "testing")
{
    secString1.AppendChar(c);
    secString2.AppendChar(c);
}
Assert.AreEqual(secString1, secString2); // This fails
Run Code Online (Sandbox Code Playgroud)

我在想比较PasswordPasswordBoxes 的属性会破坏访问点,SecurePassword因为我正在阅读纯文本密码.如何在不牺牲安全性的情况下比较两个密码?

编辑:根据这个问题,我正在查看这篇关于"使用Marshal类将SecureString转换为ANSI或Unicode或BSTR"的博文,然后我可以比较一下.

Nik*_*vak 33

这没有不安全的块,也不会以明文显示密码:

public static bool IsEqualTo(this SecureString ss1, SecureString ss2)
{
 IntPtr bstr1 = IntPtr.Zero;
 IntPtr bstr2 = IntPtr.Zero;
 try
 {
  bstr1 = Marshal.SecureStringToBSTR(ss1);
  bstr2 = Marshal.SecureStringToBSTR(ss2);
  int length1 = Marshal.ReadInt32(bstr1, -4);
  int length2 = Marshal.ReadInt32(bstr2, -4);
  if (length1 == length2)
  {
   for (int x = 0; x < length1; ++x)
   {
    byte b1 = Marshal.ReadByte(bstr1, x);
    byte b2 = Marshal.ReadByte(bstr2, x);
    if (b1 != b2) return false;
   }
  }
  else return false;
  return true;
 }
 finally
 {
  if (bstr2 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr2);
  if (bstr1 != IntPtr.Zero) Marshal.ZeroFreeBSTR(bstr1);
 }
}
Run Code Online (Sandbox Code Playgroud)

编辑:修正Alex J.推荐的泄漏

  • @NikolaNovak,好吧,我想我只是不知道当你说“并且不会以明文形式显示密码”时你的答案是什么意思,因为 `Marshal.SecureStringToBSTR` 确实解密了 `SecureString` 的内容并加载返回内存地址处的解密副本,MS 将其称为“非托管内存中的明文字符串”https://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110)。 aspx#interop (2认同)

SwD*_*n81 18

看起来你可以用来比较两者SecureStrings.

它使用不安全的代码来遍历字符串:

bool SecureStringEqual(SecureString s1, SecureString s2)  
{  
    if (s1 == null)  
    {  
        throw new ArgumentNullException("s1");  
    }  
    if (s2 == null)  
    {  
        throw new ArgumentNullException("s2");  
    }  

    if (s1.Length != s2.Length)  
    {  
        return false;  
    }  

    IntPtr bstr1 = IntPtr.Zero;  
    IntPtr bstr2 = IntPtr.Zero;  

    RuntimeHelpers.PrepareConstrainedRegions();  

    try 
    {  
        bstr1 = Marshal.SecureStringToBSTR(s1);  
        bstr2 = Marshal.SecureStringToBSTR(s2);  

        unsafe 
        {  
            for (Char* ptr1 = (Char*)bstr1.ToPointer(), ptr2 = (Char*)bstr2.ToPointer();  
                *ptr1 != 0 && *ptr2 != 0;  
                 ++ptr1, ++ptr2)  
            {  
                if (*ptr1 != *ptr2)  
                {  
                    return false;  
                }  
            }  
        }  

        return true;  
    }  
    finally 
    {  
        if (bstr1 != IntPtr.Zero)  
        {  
            Marshal.ZeroFreeBSTR(bstr1);  
        }  

        if (bstr2 != IntPtr.Zero)  
        {  
            Marshal.ZeroFreeBSTR(bstr2);  
        }  
    }  
} 
Run Code Online (Sandbox Code Playgroud)

我已将其修改为以下工作而没有不安全的代码(请注意,您可以在调试时以纯文本格式查看字符串):

  Boolean SecureStringEqual(SecureString secureString1, SecureString secureString2)
  {
     if (secureString1 == null)
     {
        throw new ArgumentNullException("s1");
     }
     if (secureString2 == null)
     {
        throw new ArgumentNullException("s2");
     }

     if (secureString1.Length != secureString2.Length)
     {
        return false;
     }

     IntPtr ss_bstr1_ptr = IntPtr.Zero;
     IntPtr ss_bstr2_ptr = IntPtr.Zero;

     try
     {
        ss_bstr1_ptr = Marshal.SecureStringToBSTR(secureString1);
        ss_bstr2_ptr = Marshal.SecureStringToBSTR(secureString2);

        String str1 = Marshal.PtrToStringBSTR(ss_bstr1_ptr);
        String str2 = Marshal.PtrToStringBSTR(ss_bstr2_ptr);

        return str1.Equals(str2);
     }
     finally
     {
        if (ss_bstr1_ptr != IntPtr.Zero)
        {
           Marshal.ZeroFreeBSTR(ss_bstr1_ptr);
        }

        if (ss_bstr2_ptr != IntPtr.Zero)
        {
           Marshal.ZeroFreeBSTR(ss_bstr2_ptr);
        }
     }
  }
Run Code Online (Sandbox Code Playgroud)

  • @SwDevMan:yikes,这个肯定有效并且没有`unsafe`块,但是通过调试器逐步执行它会显示那两个`str1`和`str2`字符串最后是纯文本的敏感数据.我更喜欢`unsafe`版本,因为它只是比较指针; 使用调试器逐步执行它,我没有看到纯文本中可理解的数据. (6认同)
  • 但是,`SecureString`不会覆盖`Equals`,因此这也只是检查引用是否相等。 (2认同)