diff --git a/src/BCrypt.Net.MainPackage/BCrypt.Net.Package.csproj b/src/BCrypt.Net.MainPackage/BCrypt.Net.Package.csproj
index 4abbbac..171fe34 100644
--- a/src/BCrypt.Net.MainPackage/BCrypt.Net.Package.csproj
+++ b/src/BCrypt.Net.MainPackage/BCrypt.Net.Package.csproj
@@ -71,4 +71,8 @@
bin\$(Configuration)\$(TargetFramework)\BCrypt.Net-Next.xml
+
+ true
+
+
diff --git a/src/BCrypt.Net.StrongName/BCrypt.Net.StrongName.csproj b/src/BCrypt.Net.StrongName/BCrypt.Net.StrongName.csproj
index ffee818..cfa7d4e 100644
--- a/src/BCrypt.Net.StrongName/BCrypt.Net.StrongName.csproj
+++ b/src/BCrypt.Net.StrongName/BCrypt.Net.StrongName.csproj
@@ -71,4 +71,8 @@
bin\$(Configuration)\$(TargetFramework)\BCrypt.Net-Next.xml
+
+ true
+
+
diff --git a/src/BCrypt.Net.UnitTests/BCrypt.Net.UnitTests.csproj b/src/BCrypt.Net.UnitTests/BCrypt.Net.UnitTests.csproj
index 89eecae..e3301a4 100644
--- a/src/BCrypt.Net.UnitTests/BCrypt.Net.UnitTests.csproj
+++ b/src/BCrypt.Net.UnitTests/BCrypt.Net.UnitTests.csproj
@@ -10,6 +10,10 @@
1701;1702;CS1591
+
+ true
+
+
diff --git a/src/BCrypt.Net.UnitTests/BCryptTests.cs b/src/BCrypt.Net.UnitTests/BCryptTests.cs
index 33e02a0..802d162 100644
--- a/src/BCrypt.Net.UnitTests/BCryptTests.cs
+++ b/src/BCrypt.Net.UnitTests/BCryptTests.cs
@@ -19,6 +19,7 @@ IN THE SOFTWARE.
using System;
using System.Diagnostics;
+using System.Security;
using System.Security.Cryptography;
using System.Text;
using Xunit;
@@ -145,6 +146,62 @@ public void TestHashPassword()
}
+ private SecureString AsSecureString(string text)
+ {
+ var result = new SecureString();
+ foreach (var c in text) result.AppendChar(c);
+ result.MakeReadOnly();
+ return result;
+ }
+
+
+ /**
+ * Test method for 'BCrypt.HashPassword(SecureString, string)'
+ */
+ [Fact()]
+ public void TestSecureHashPassword()
+ {
+ Trace.Write("BCrypt.HashPassword()[Secure]: ");
+ var sw = Stopwatch.StartNew();
+ for (var r = 0; r < _revisions.Length; r++)
+ {
+ for (int i = 0; i < _testVectors.Length / 3; i++)
+ {
+ var plain = AsSecureString(_testVectors[i, 0]);
+ string salt;
+ string expected;
+ if (r > 0)
+ {
+ //Check hash that goes in one end comes out the next the same
+ salt = _testVectors[i, 1].Replace("2a", "2" + _revisions[r]);
+
+ string hashed = BCrypt.HashPassword(plain, salt);
+
+
+ var d = hashed.StartsWith("$2" + _revisions[r]);
+ Assert.True(d);
+ Trace.WriteLine(hashed);
+ }
+ else
+ {
+ salt = _testVectors[i, 1];
+ expected = _testVectors[i, 2];
+
+ string hashed = BCrypt.HashPassword(plain, salt);
+ var d = hashed == expected;
+ Assert.Equal(hashed, expected);
+ }
+
+
+ Trace.Write(".");
+ }
+ }
+
+ Trace.WriteLine(sw.ElapsedMilliseconds);
+ Trace.WriteLine("");
+ }
+
+
/**
* Test method for 'BCrypt.HashPassword(string, string)'
*/
@@ -182,6 +239,42 @@ public void TestHashPasswordEnhanced()
Trace.WriteLine("");
}
+ [Fact()]
+ public void TestSecureHashPasswordEnhanced()
+ {
+ Trace.Write("BCrypt.HashPassword()[Secure]: ");
+ var sw = Stopwatch.StartNew();
+ for (var r = 0; r < _revisions.Length; r++)
+ {
+ for (int i = 0; i < _testVectors.Length / 3; i++)
+ {
+ var plain = _testVectors[i, 0];
+ string salt;
+
+ //Check hash that goes in one end comes out the next the same
+ salt = _testVectors[i, 1].Replace("2a", "2" + _revisions[r]);
+
+ string hashed = BCrypt.HashPassword(plain, salt, enhancedEntropy: true);
+ string secureHashed = BCrypt.HashPassword(AsSecureString(plain), salt, enhancedEntropy: true);
+
+ var revCheck = hashed.StartsWith("$2" + _revisions[r]);
+
+ Assert.True(revCheck);
+ Assert.Equal(hashed, secureHashed);
+
+ var validateHashCheck = BCrypt.EnhancedVerify(AsSecureString(plain), hashed);
+ Assert.True(validateHashCheck);
+
+ Trace.WriteLine(hashed);
+
+ Trace.Write(".");
+ }
+ }
+
+ Trace.WriteLine(sw.ElapsedMilliseconds);
+ Trace.WriteLine("");
+ }
+
[Fact()]
public void TestHashPasswordEnhancedWithHashType()
{
@@ -216,6 +309,42 @@ public void TestHashPasswordEnhancedWithHashType()
Trace.WriteLine("");
}
+ [Fact()]
+ public void TestSecureHashPasswordEnhancedWithHashType()
+ {
+ Trace.Write("BCrypt.HashPassword()[Secure]: ");
+ var sw = Stopwatch.StartNew();
+ for (var r = 0; r < _revisions.Length; r++)
+ {
+ for (int i = 0; i < _testVectors.Length / 3; i++)
+ {
+ var plain = _testVectors[i, 0];
+ string salt;
+
+ //Check hash that goes in one end comes out the next the same
+ salt = _testVectors[i, 1].Replace("2a", "2" + _revisions[r]);
+
+ string hashed = BCrypt.HashPassword(plain, salt, true, HashType.SHA256);
+ string secureHashed = BCrypt.HashPassword(AsSecureString(plain), salt, true, HashType.SHA256);
+
+ var revCheck = hashed.StartsWith("$2" + _revisions[r]);
+
+ Assert.True(revCheck);
+ Assert.Equal(hashed, secureHashed);
+
+ var validateHashCheck = BCrypt.EnhancedVerify(AsSecureString(plain), hashed, HashType.SHA256);
+ Assert.True(validateHashCheck);
+
+ Trace.WriteLine(hashed);
+
+ Trace.Write(".");
+ }
+ }
+
+ Trace.WriteLine(sw.ElapsedMilliseconds);
+ Trace.WriteLine("");
+ }
+
[Fact()]
public void TestValidateAndReplace()
{
@@ -240,6 +369,30 @@ public void TestValidateAndReplace()
}
+ [Fact()]
+ public void TestSecureValidateAndReplace()
+ {
+ for (int i = 0; i < _testVectors.Length / 3; i++)
+ {
+ var currentKey = AsSecureString(_testVectors[i, 0]);
+ string salt = _testVectors[i, 1];
+ string currentHash = _testVectors[i, 2];
+
+ var newPassword = AsSecureString("my new password");
+ string hashed = BCrypt.HashPassword(currentKey, salt);
+ var d = hashed == currentHash;
+
+ var newHash = BCrypt.ValidateAndReplacePassword(currentKey, currentHash, newPassword);
+
+ var newPassValid = BCrypt.Verify(newPassword, newHash);
+
+ Assert.True(newPassValid);
+
+ Trace.Write(".");
+ }
+
+ }
+
[Theory()]
[InlineData("\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605")]
diff --git a/src/BCrypt.Net/BCrypt.Net.csproj b/src/BCrypt.Net/BCrypt.Net.csproj
index 99958b0..4227c33 100644
--- a/src/BCrypt.Net/BCrypt.Net.csproj
+++ b/src/BCrypt.Net/BCrypt.Net.csproj
@@ -51,6 +51,10 @@
bin\$(Configuration)\$(TargetFramework)\BCrypt.Net-Next.xml
+
+ true
+
+
diff --git a/src/BCrypt.Net/BCrypt.cs b/src/BCrypt.Net/BCrypt.cs
index cb44ad1..9aaaa9a 100644
--- a/src/BCrypt.Net/BCrypt.cs
+++ b/src/BCrypt.Net/BCrypt.cs
@@ -19,6 +19,8 @@ IN THE SOFTWARE.
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
@@ -407,6 +409,296 @@ public sealed class BCrypt
private uint[] _p;
private uint[] _s;
+ // SecureString support for inputKey
+ //
+ //
+ // SecureString is an arguably poorly implemented idea as .Net created it and sometimes requires us to use it but lacks
+ // support in man places where it would be useful, and it can be useful; like moving passwords around.
+ // So, what we are supposed to know is that moving the clear text version of the SecureString into a managed string opens
+ // up the possibility of putting a copy into memory out of our control - and we pretty much have to put it in a managed
+ // string to do anything with it! So, the trick is to pin a managed string to take it away from the GC so as to not inadvertently
+ // makes copies, use it, then wipe it out before letting GC have it back. Basically narrow the clear text window.
+ //
+ //
+ // Look for the "SecureString COMMENT"s where I make some observations.
+ //
+ // Oh, and this whole thing isn't supposed to be super fast, so, there's that.
+ //
+ //
+ //
+
+ #region SecureString support for inputKey
+
+ ///
+ /// Verifies that the hash of the given matches the provided
+ /// ; the string will undergo SHA384 hashing to maintain the enhanced entropy work done during hashing
+ ///
+ /// The text to verify.
+ /// The previously-hashed password.
+ /// HashType used (default SHA384)
+ /// true if the passwords match, false otherwise.
+ public static bool EnhancedVerify(SecureString text, string hash, HashType hashType = DefaultEnhancedHashType) => Verify(text, hash, true, hashType);
+
+ ///
+ /// Verifies that the hash of the given matches the provided
+ ///
+ ///
+ /// The text to verify.
+ /// The previously-hashed password.
+ /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing
+ /// HashType used (default SHA384)
+ /// true if the passwords match, false otherwise.
+ /// Thrown when one or more arguments have unsupported or illegal values.
+ /// Thrown when the salt could not be parsed.
+ public static bool Verify(SecureString text, string hash, bool enhancedEntropy = false, HashType hashType = DefaultEnhancedHashType)
+ => VerifyHashes(hash, HashPassword(text, hash, enhancedEntropy, hashType));
+
+
+ ///
+ /// Validate existing hash and password,
+ ///
+ /// Current password / string
+ /// Current hash to validate password against
+ /// NEW password / string to be hashed
+ /// The log2 of the number of rounds of hashing to apply - the work
+ /// factor therefore increases as 2^workFactor. Default is 11
+ /// By default this method will not accept a work factor lower
+ /// than the one set in the current hash and will set the new work-factor to match.
+ /// returned if the users hash and current pass doesn't validate
+ /// returned if the salt is invalid in any way
+ /// returned if the hash is invalid
+ /// returned if the user hash is null
+ /// New hash of new password
+ public static string ValidateAndReplacePassword(SecureString currentKey, string currentHash, SecureString newKey, int workFactor = DefaultRounds, bool forceWorkFactor = false) =>
+ ValidateAndReplacePassword(currentKey, currentHash, false, HashType.None, newKey, false, HashType.None, workFactor, forceWorkFactor);
+
+
+ ///
+ /// Validate existing hash and password,
+ ///
+ /// Current password / string
+ /// Current hash to validate password against
+ /// Set to true,the string will undergo SHA384 hashing to make
+ /// use of available entropy prior to bcrypt hashing
+ /// HashType used (default SHA384)
+ ///
+ /// NEW password / string to be hashed
+ /// Set to true,the string will undergo SHA384 hashing to make
+ /// use of available entropy prior to bcrypt hashing
+ /// HashType to use (default SHA384)
+ /// The log2 of the number of rounds of hashing to apply - the work
+ /// factor therefore increases as 2^workFactor. Default is 11
+ /// By default this method will not accept a work factor lower
+ /// than the one set in the current hash and will set the new work-factor to match.
+ /// returned if the users hash and current pass doesn't validate
+ /// returned if the salt is invalid in any way
+ /// returned if the hash is invalid
+ /// returned if the user hash is null
+ /// New hash of new password
+ public static string ValidateAndReplacePassword(SecureString currentKey, string currentHash, bool currentKeyEnhancedEntropy, HashType oldHashType,
+ SecureString newKey, bool newKeyEnhancedEntropy = false, HashType newHashType = DefaultEnhancedHashType, int workFactor = DefaultRounds, bool forceWorkFactor = false)
+ {
+ return SandboxSecureString(
+ currentKey,
+ currentClear =>
+ {
+ return SandboxSecureString(
+ newKey,
+ newClear =>
+ {
+ return ValidateAndReplacePassword(
+ currentClear,
+ currentHash,
+ currentKeyEnhancedEntropy,
+ oldHashType,
+ newClear,
+ newKeyEnhancedEntropy,
+ newHashType,
+ workFactor,
+ forceWorkFactor);
+ });
+ });
+ }
+
+ ///
+ /// Hash a password using the OpenBSD BCrypt scheme and a salt generated by .
+ ///
+ /// The password to hash.
+ /// The hashed password.
+ /// Thrown when the salt could not be parsed.
+ public static string HashPassword(SecureString inputKey) => HashPassword(inputKey, GenerateSalt());
+
+ ///
+ /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
+ ///
+ /// The password to hash.
+ /// The hashed password.
+ /// Thrown when the salt could not be parsed.
+ public static string EnhancedHashPassword(SecureString inputKey) => HashPassword(inputKey, GenerateSalt(), true);
+
+ ///
+ /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
+ ///
+ /// The password to hash.
+ ///
+ /// The hashed password.
+ /// Thrown when the salt could not be parsed.
+ public static string EnhancedHashPassword(SecureString inputKey, int workFactor) => HashPassword(inputKey, GenerateSalt(workFactor), true);
+
+ ///
+ /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
+ ///
+ /// The password to hash.
+ ///
+ /// Configurable hash type for enhanced entropy
+ /// The hashed password.
+ /// Thrown when the salt could not be parsed.
+ public static string EnhancedHashPassword(SecureString inputKey, int workFactor, HashType hashType) => HashPassword(inputKey, GenerateSalt(workFactor), true, hashType);
+
+ ///
+ /// Pre-hash a password with SHA384 then using the OpenBSD BCrypt scheme and a salt generated by .
+ ///
+ /// The password to hash.
+ /// Defaults to 11
+ /// Configurable hash type for enhanced entropy
+ /// The hashed password.
+ /// Thrown when the salt could not be parsed.
+ public static string EnhancedHashPassword(SecureString inputKey, HashType hashType, int workFactor = DefaultRounds) => HashPassword(inputKey, GenerateSalt(workFactor), true, hashType);
+
+ ///
+ /// Hash a password using the OpenBSD BCrypt scheme and a salt generated by using the given .
+ ///
+ /// The password to hash.
+ /// The log2 of the number of rounds of hashing to apply - the work
+ /// factor therefore increases as 2^workFactor. Default is 11
+ /// Set to true,the string will undergo SHA384 hashing to make use of available entropy prior to bcrypt hashing
+ /// The hashed password.
+ /// Thrown when the salt could not be parsed.
+ public static string HashPassword(SecureString inputKey, int workFactor, bool enhancedEntropy = false) => HashPassword(inputKey, GenerateSalt(workFactor), enhancedEntropy);
+
+ /// Hash a password using the OpenBSD BCrypt scheme.
+ /// Thrown when one or more arguments have unsupported or illegal values.
+ /// The password or string to hash.
+ /// the salt to hash with (best generated using ).
+ /// The hashed password
+ /// Thrown when the could not be parsed.
+ public static string HashPassword(SecureString inputKey, string salt) => HashPassword(inputKey, salt, false);
+
+ /// Hash a password using the OpenBSD BCrypt scheme.
+ ///
+ /// based on an answer by sclarke81 in https://stackoverflow.com/questions/818704/how-to-convert-securestring-to-system-string
+ /// Yes, the SecureString becomes a managed string, but we are managing its lifetime in memory.
+ ///
+ /// Thrown when one or more arguments have unsupported or illegal values.
+ /// The password or string to hash.
+ /// the salt to hash with (best generated using ).
+ /// Set to true,the string will undergo hashing (defaults to SHA384 then base64 encoding) to make use of available entropy prior to bcrypt hashing
+ /// Configurable hash type for enhanced entropy
+ /// The hashed password
+ /// Thrown when the is null.
+ /// Thrown when the could not be parsed.
+ public static string HashPassword(SecureString inputKey, string salt, bool enhancedEntropy, HashType hashType = DefaultEnhancedHashType)
+ {
+ return SandboxSecureString(
+ inputKey,
+ clearText =>
+ {
+ return HashPassword(clearText, salt, enhancedEntropy, hashType);
+ });
+ }
+
+ // .net 2.0 safe - basically Func
+ private delegate string UseSandboxedSecureString(string inputKey);
+
+ private static string SandboxSecureString(SecureString inputKey, UseSandboxedSecureString func)
+ {
+ if (inputKey == null)
+ {
+ throw new ArgumentNullException(nameof(inputKey));
+ }
+
+ int length = inputKey.Length;
+ IntPtr sourceStringPointer = IntPtr.Zero;
+
+ // Create an empty string of the correct size and pin it so that the GC can't move it around.
+ string insecureString = new string('\0', length);
+ var insecureStringHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);
+
+ IntPtr insecureStringPointer = insecureStringHandler.AddrOfPinnedObject();
+
+ try
+ {
+ // Create an unmanaged copy of the secure string.
+ sourceStringPointer = Marshal.SecureStringToBSTR(inputKey);
+
+ // Use the pointers to copy from the unmanaged to managed string.
+ for (int i = 0; i < inputKey.Length; i++)
+ {
+ short unicodeChar = Marshal.ReadInt16(sourceStringPointer, i * 2);
+ Marshal.WriteInt16(insecureStringPointer, i * 2, unicodeChar);
+ }
+
+ return func(insecureString);
+ }
+ finally
+ {
+ // Zero the managed string so that the string is erased. Then unpin it to allow the
+ // GC to take over.
+ Marshal.Copy(new byte[length * 2], 0, insecureStringPointer, length * 2);
+ insecureStringHandler.Free();
+
+ // Zero and free the unmanaged string.
+ Marshal.ZeroFreeBSTR(sourceStringPointer);
+ }
+ }
+
+ // .net 2.0 safe - basically Func
+ private delegate string PerformHashDelegate(byte[] inputBytes);
+
+ /// Do our own managing of a utf8 copy of the password text.
+ /// The password or string to hash.
+ /// added to the end of inputKey
+ /// function called with pinned utf8
+ /// func result
+ private unsafe static string SandboxUTF8Copy(string inputKey, string addendum, PerformHashDelegate func)
+ {
+ var maxBytes = SafeUTF8.GetMaxByteCount(inputKey.Length + addendum.Length);
+ var utf8Buffer = Marshal.AllocHGlobal(maxBytes);
+ var utf8Pointer = (byte*)utf8Buffer.ToPointer();
+ var inputKeyHandler = GCHandle.Alloc(inputKey, GCHandleType.Pinned);
+ var inputKeyPointer = (char*)inputKeyHandler.AddrOfPinnedObject().ToPointer();
+ var addendumHandler = GCHandle.Alloc(addendum, GCHandleType.Pinned);
+ var addendumPointer = (char*)addendumHandler.AddrOfPinnedObject().ToPointer();
+
+ try
+ {
+ var utf8Bytes = Encoding.UTF8.GetBytes(inputKeyPointer, inputKey.Length, utf8Pointer, maxBytes);
+ utf8Bytes += Encoding.UTF8.GetBytes(addendumPointer, addendum.Length, utf8Pointer + utf8Bytes, maxBytes - utf8Bytes);
+
+ var result = new byte[utf8Bytes];
+ var utf8BytesPin = GCHandle.Alloc(result, GCHandleType.Pinned);
+ try
+ {
+ Marshal.Copy(utf8Buffer, result, 0, utf8Bytes);
+ Marshal.Copy(new byte[maxBytes], 0, utf8Buffer, maxBytes);
+
+ return func(result);
+ }
+ finally
+ {
+ Marshal.Copy(new byte[utf8Bytes], 0, utf8BytesPin.AddrOfPinnedObject(), utf8Bytes);
+ utf8BytesPin.Free();
+ }
+ }
+ finally
+ {
+ inputKeyHandler.Free();
+ addendumHandler.Free();
+ Marshal.FreeHGlobal(utf8Buffer);
+ }
+ }
+
+ #endregion
///
/// Validate existing hash and password,
@@ -599,6 +891,10 @@ public static string ValidateAndReplacePassword(string currentKey, string curren
/// Thrown when the could not be parsed.
public static string HashPassword(string inputKey, string salt, bool enhancedEntropy, HashType hashType = DefaultEnhancedHashType)
{
+ // SecureString COMMENT
+ // DO NOT DO ANYTHING WITH inputKey lest thee drop a copy in managed memory
+ // Let our use of it happen in the sandboxed utf8 scope
+
if (inputKey == null)
{
throw new ArgumentNullException(nameof(inputKey));
@@ -652,26 +948,33 @@ public static string HashPassword(string inputKey, string salt, bool enhancedEnt
string extractedSalt = salt.Substring(startingOffset + 3, 22);
- byte[] inputBytes = SafeUTF8.GetBytes(inputKey + (minor >= 'a' ? Nul : EmptyString));
+ // originally this - which makes a managed string copy...
+ //byte[] inputBytes = SafeUTF8.GetBytes(inputKey + (minor >= 'a' ? Nul : EmptyString));
- if (enhancedEntropy)
- {
- inputBytes = EnhancedHash(inputBytes, hashType);
- }
+ return SandboxUTF8Copy(
+ inputKey,
+ minor >= 'a' ? Nul : EmptyString,
+ inputBytes => // SecureString COMMENT - this is a reference to the pinned byte[]
+ {
+ if (enhancedEntropy)
+ {
+ inputBytes = EnhancedHash(inputBytes, hashType); // SecureString COMMENT - changes the reference to a managed hash byte[]
+ }
- byte[] saltBytes = DecodeBase64(extractedSalt, BCryptSaltLen);
+ byte[] saltBytes = DecodeBase64(extractedSalt, BCryptSaltLen);
- BCrypt bCrypt = new BCrypt();
+ BCrypt bCrypt = new BCrypt();
- byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, workFactor);
+ byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, workFactor); // SecureString COMMENT - I did not find any buffer copying from here - be careful
- // Generate result string
- StringBuilder result = new StringBuilder();
- result.AppendFormat("$2{1}${0:00}$", workFactor, minor);
- result.Append(EncodeBase64(saltBytes, saltBytes.Length));
- result.Append(EncodeBase64(hashed, (BfCryptCiphertext.Length * 4) - 1));
+ // Generate result string
+ StringBuilder result = new StringBuilder();
+ result.AppendFormat("$2{1}${0:00}$", workFactor, minor);
+ result.Append(EncodeBase64(saltBytes, saltBytes.Length));
+ result.Append(EncodeBase64(hashed, (BfCryptCiphertext.Length * 4) - 1));
- return result.ToString();
+ return result.ToString();
+ });
}
///
@@ -682,6 +985,13 @@ public static string HashPassword(string inputKey, string salt, bool enhancedEnt
///
private static byte[] EnhancedHash(byte[] inputBytes, HashType hashType)
{
+
+ // SecureString COMMENT
+ // We have to trust that these hash functions don't make a copy of inputBytes. I checked the \referencesource on github
+ // and the managed version of SHA256, SHA384, and SHA512 do not. I figured the managed version would be worst case.
+ // Now, keep in mind that the hash they produce will be in managed memory and possibly leaky. Without implementing
+ // them ourself we'll just have to accept this compromise.
+
switch (hashType)
{
case HashType.SHA256:
@@ -834,8 +1144,17 @@ public static string GenerateSalt()
/// Thrown when one or more arguments have unsupported or illegal values.
/// Thrown when the salt could not be parsed.
public static bool Verify(string text, string hash, bool enhancedEntropy = false, HashType hashType = DefaultEnhancedHashType)
+ => VerifyHashes(hash, HashPassword(text, hash, enhancedEntropy, hashType));
+
+ ///
+ /// Verifies same hash values
+ ///
+ /// a hashed password
+ /// another hashed password
+ /// true if hashes are equal
+ private static bool VerifyHashes(string hashA, string hashB)
{
- return SecureEquals(SafeUTF8.GetBytes(hash), SafeUTF8.GetBytes(HashPassword(text, hash, enhancedEntropy, hashType)));
+ return SecureEquals(SafeUTF8.GetBytes(hashA), SafeUTF8.GetBytes(hashB));
}
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimised.