C# - 比较两个证券的平等
-
12-10-2019 - |
题
我有一个带有两个密码盒的WPF应用程序,一个用于密码,另一个用于第二次输入密码以确认目的。我想使用 PasswordBox.SecurePassword
得到 SecureString
密码,但是我需要能够比较两个密码框的内容,以确保在接受密码之前保持平等。但是,两个相同的证券仪式不相等:
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
我在想比较 Password
密码盒的属性将失败仅访问的点 SecurePassword
因为我会阅读普通文本密码。我应该怎么做才能在不牺牲的情况下比较两个密码 安全?
编辑: : 基于 这个问题, , 我正在结账 这篇博客文章 关于“使用元帅将Securestring转换为ANSI或Unicode或BSTR”,然后我可以比较这些。
解决方案
看起来您可以使用 这个 比较两者 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);
}
}
}
我在下面修改了它的工作,没有不安全的代码(请注意,在调试时您可以在纯文本中看到字符串):
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);
}
}
}
其他提示
这没有不安全的块,也不会以明文显示密码:
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);
}
}
编辑: 修复了Alex J.建议的泄漏。
翻译 @nikolanovák答案简单的powershell:
param(
[Parameter(mandatory=$true,position=0)][SecureString]$ss1,
[Parameter(mandatory=$true,position=1)][SecureString]$ss2
)
function IsEqualTo{
param(
[Parameter(mandatory=$true,position=0)][SecureString]$ss1,
[Parameter(mandatory=$true,position=1)][SecureString]$ss2
)
begin{
[IntPtr] $bstr1 = [IntPtr]::Zero;
[IntPtr] $bstr2 = [IntPtr]::Zero;
[bool]$answer=$true;
}
process{
try{
$bstr1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss1);
$bstr2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss2);
[int]$length1 = [System.Runtime.InteropServices.Marshal]::ReadInt32($bstr1, -4);
[int]$length2 = [System.Runtime.InteropServices.Marshal]::ReadInt32($bstr2, -4);
if ($length1 -eq $length2){
for ([int]$x -eq 0; $x -lt $length1; ++$x){
[byte]$b1 = [System.Runtime.InteropServices.Marshal]::ReadByte($bstr1, $x);
[byte]$b2 = [System.Runtime.InteropServices.Marshal]::ReadByte($bstr2, $x);
if ($b1 -ne $b2){
$answer=$false;
}
}
}
else{ $answer=$false;}
}
catch{
}
finally
{
if ($bstr2 -ne [IntPtr]::Zero){ [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2)};
if ($bstr1 -ne [IntPtr]::Zero){ [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1)};
}
}
END{
return $answer
}
}
IsEqualTo -ss1 $ss1 -ss2 $ss2
您可以采取不同的方法。我在代码中遇到了相同的问题,密码的比较和确认,这两种类型 Securestring. 。我意识到最终目标是需要将新密码作为基本64字符串存储在数据库中。因此,我所做的只是将确认字符串通过相同的代码,就像我要将其写入数据库一样。然后,当我有两个基本64字符串时,我在此时进行比较,这是一个简单的字符串比较。
将任何故障一直返回到UI层确实需要更多的管道,但是最终结果似乎是可以接受的。希望该代码足以给出基本想法。
private string CalculateHash( SecureString securePasswordString, string saltString )
{
IntPtr unmanagedString = IntPtr.Zero;
try
{
unmanagedString = Marshal.SecureStringToGlobalAllocUnicode( securePasswordString );
byte[] passwordBytes = Encoding.UTF8.GetBytes( Marshal.PtrToStringUni( unmanagedString ) );
byte[] saltBytes = Encoding.UTF8.GetBytes( saltString );
byte[] passwordPlusSaltBytes = new byte[ passwordBytes.Length + saltBytes.Length ];
Buffer.BlockCopy( passwordBytes, 0, passwordPlusSaltBytes, 0, passwordBytes.Length );
Buffer.BlockCopy( saltBytes, 0, passwordPlusSaltBytes, passwordBytes.Length, saltBytes.Length );
HashAlgorithm algorithm = new SHA256Managed();
return Convert.ToBase64String( algorithm.ComputeHash( passwordPlusSaltBytes ) );
}
finally
{
if( unmanagedString != IntPtr.Zero )
Marshal.ZeroFreeGlobalAllocUnicode( unmanagedString );
}
}
string passwordSalt = "INSERT YOUR CHOSEN METHOD FOR CONSTRUCTING A PASSWORD SALT HERE";
string passwordHashed = CalculateHash( securePasswordString, passwordSalt );
string confirmPasswordHashed = CalculateHash( secureConfirmPasswordString, passwordSalt );
if( passwordHashed == confirmPasswordHashed )
{
// Both matched so go ahead and persist the new password.
}
else
{
// Strings don't match, so communicate the failure back to the UI.
}
我在安全节目中有点新手,因此我欢迎任何改进建议。
如果代码在Windows Vista或更高版本上运行,以下是基于 比较曲目 Windows功能,因此没有纯文本,所有缓冲区都保持不受管理。奖金是支持细菌不敏感的比较。
public static bool EqualsOrdinal(this SecureString text1, SecureString text2, bool ignoreCase = false)
{
if (text1 == text2)
return true;
if (text1 == null)
return text2 == null;
if (text2 == null)
return false;
if (text1.Length != text2.Length)
return false;
var b1 = IntPtr.Zero;
var b2 = IntPtr.Zero;
try
{
b1 = Marshal.SecureStringToBSTR(text1);
b2 = Marshal.SecureStringToBSTR(text2);
return CompareStringOrdinal(b1, text1.Length, b2, text2.Length, ignoreCase) == CSTR_EQUAL;
}
finally
{
if (b1 != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(b1);
}
if (b2 != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(b2);
}
}
}
public static bool EqualsOrdinal(this SecureString text1, string text2, bool ignoreCase = false)
{
if (text1 == null)
return text2 == null;
if (text2 == null)
return false;
if (text1.Length != text2.Length)
return false;
var b = IntPtr.Zero;
try
{
b = Marshal.SecureStringToBSTR(text1);
return CompareStringOrdinal(b, text1.Length, text2, text2.Length, ignoreCase) == CSTR_EQUAL;
}
finally
{
if (b != IntPtr.Zero)
{
Marshal.ZeroFreeBSTR(b);
}
}
}
private const int CSTR_EQUAL = 2;
[DllImport("kernel32")]
private static extern int CompareStringOrdinal(IntPtr lpString1, int cchCount1, IntPtr lpString2, int cchCount2, bool bIgnoreCase);
[DllImport("kernel32")]
private static extern int CompareStringOrdinal(IntPtr lpString1, int cchCount1, [MarshalAs(UnmanagedType.LPWStr)] string lpString2, int cchCount2, bool bIgnoreCase);