Generalize password hash comparison
This commit is contained in:
parent
8b373ebfe8
commit
8405d09992
2 changed files with 61 additions and 29 deletions
|
|
@ -33,10 +33,9 @@ namespace CapyKit.Helpers
|
|||
|
||||
public static bool CompareHashedPassword<T>(Password existingPassword, string password, string salt,
|
||||
params object[] args)
|
||||
where T : IPasswordAlgorithm
|
||||
{
|
||||
var saltBytes = Convert.FromBase64String(salt);
|
||||
|
||||
return CompareHashedPassword<T>(existingPassword, password, saltBytes, args);
|
||||
return CompareHashedPassword<T>(Convert.ToBase64String(existingPassword.Hash), password, salt, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -54,13 +53,61 @@ namespace CapyKit.Helpers
|
|||
/// </returns>
|
||||
public static bool CompareHashedPassword<T>(Password existingPassword, string password, byte[] salt,
|
||||
params object[] args)
|
||||
where T : IPasswordAlgorithm
|
||||
{
|
||||
var providedPassword = typeof(SecurityHelper)
|
||||
.GetMethod("GetPassword", new Type[] { typeof(string), typeof(byte[]), typeof(object[]) })
|
||||
?.MakeGenericMethod(typeof(T))
|
||||
?.Invoke(null, new object[] { password, salt, args });
|
||||
return CompareHashedPassword<T>(existingPassword.Hash, password, salt, args);
|
||||
}
|
||||
|
||||
return existingPassword.Equals(providedPassword);
|
||||
/// <summary>
|
||||
/// Compares a plaintext password with a persisted Base64-encoded hash and salt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The password hashing algorithm.</typeparam>
|
||||
/// <param name="storedHash">The Base64-encoded persisted hash.</param>
|
||||
/// <param name="password">The plaintext password to verify.</param>
|
||||
/// <param name="salt">The Base64-encoded persisted salt.</param>
|
||||
/// <param name="args">Arguments used to construct the password algorithm.</param>
|
||||
/// <returns><see langword="true"/> when the password matches the persisted hash.</returns>
|
||||
public static bool CompareHashedPassword<T>(string storedHash, string password, string salt,
|
||||
params object[] args)
|
||||
where T : IPasswordAlgorithm
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(storedHash) || string.IsNullOrEmpty(password) || string.IsNullOrWhiteSpace(salt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return CompareHashedPassword<T>(Convert.FromBase64String(storedHash), password,
|
||||
Convert.FromBase64String(salt), args);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a plaintext password with a persisted hash and salt.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The password hashing algorithm.</typeparam>
|
||||
/// <param name="storedHash">The persisted hash.</param>
|
||||
/// <param name="password">The plaintext password to verify.</param>
|
||||
/// <param name="salt">The persisted salt.</param>
|
||||
/// <param name="args">Arguments used to construct the password algorithm.</param>
|
||||
/// <returns><see langword="true"/> when the password matches the persisted hash.</returns>
|
||||
public static bool CompareHashedPassword<T>(byte[] storedHash, string password, byte[] salt,
|
||||
params object[] args)
|
||||
where T : IPasswordAlgorithm
|
||||
{
|
||||
if (storedHash == null || storedHash.Length == 0 || string.IsNullOrEmpty(password) || salt == null || salt.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var candidatePassword = GetPassword<T>(password, salt, args);
|
||||
return candidatePassword is not null && candidatePassword.Hash.Length == storedHash.Length &&
|
||||
CryptographicOperations.FixedTimeEquals(storedHash, candidatePassword.Hash);
|
||||
}
|
||||
|
||||
public static bool CompareHashedPassword(Password existingPassword, string password, string salt,
|
||||
|
|
@ -97,24 +144,6 @@ namespace CapyKit.Helpers
|
|||
return existingPassword.Equals(providedPassword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies a plaintext password against a persisted PBKDF2 hash and salt.
|
||||
/// </summary>
|
||||
/// <param name="password">The plaintext password supplied by the caller.</param>
|
||||
/// <param name="storedHash">The persisted PBKDF2 hash.</param>
|
||||
/// <param name="salt">The persisted PBKDF2 salt.</param>
|
||||
/// <returns><see langword="true"/> when the password matches the persisted hash.</returns>
|
||||
public static bool VerifyPbkdf2(string password, byte[] storedHash, byte[] salt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password) || storedHash == null || salt == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var candidateHash = Pbkdf2(password, salt).Hash;
|
||||
return CryptographicOperations.FixedTimeEquals(storedHash, candidateHash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces a deterministic SHA-256 digest for an opaque secret that must be looked up
|
||||
/// without persisting the raw value.
|
||||
|
|
|
|||
|
|
@ -83,14 +83,17 @@ namespace Tests.Helpers
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void VerifyPbkdf2_WithPersistedCredential_ReturnsExpectedResult()
|
||||
public void CompareHashedPassword_WithPersistedHashAndSalt_ReturnsExpectedResult()
|
||||
{
|
||||
var credential = SecurityHelper.Pbkdf2(Password, Salt);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(SecurityHelper.VerifyPbkdf2(Password, credential.Hash, credential.Salt), Is.True);
|
||||
Assert.That(SecurityHelper.VerifyPbkdf2("WrongPassword", credential.Hash, credential.Salt), Is.False);
|
||||
Assert.That(SecurityHelper.CompareHashedPassword<Pbkdf2Algorithm>(credential.Hash, Password, credential.Salt), Is.True);
|
||||
Assert.That(SecurityHelper.CompareHashedPassword<Pbkdf2Algorithm>(credential.Hash, "WrongPassword", credential.Salt), Is.False);
|
||||
Assert.That(SecurityHelper.CompareHashedPassword<Pbkdf2Algorithm>(
|
||||
Convert.ToBase64String(credential.Hash), Password, Convert.ToBase64String(credential.Salt)), Is.True);
|
||||
Assert.That(SecurityHelper.CompareHashedPassword<Pbkdf2Algorithm>("not-base64", Password, "not-base64"), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue