From 9e0fbac6d3cb4e11efdb33c339ccf0adbca0bc6c Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Thu, 1 Apr 2021 22:36:59 +0200 Subject: Initial commit --- src/IOL.Helpers/PasswordHelpers.cs | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/IOL.Helpers/PasswordHelpers.cs (limited to 'src/IOL.Helpers/PasswordHelpers.cs') diff --git a/src/IOL.Helpers/PasswordHelpers.cs b/src/IOL.Helpers/PasswordHelpers.cs new file mode 100644 index 0000000..5b85219 --- /dev/null +++ b/src/IOL.Helpers/PasswordHelpers.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; + +namespace IOL.Helpers +{ + public static class PasswordHelper + { + private const int ITERATION_COUNT = 10000; + private const int SALT_SIZE = 128 / 8; + private const KeyDerivationPrf PRF = KeyDerivationPrf.HMACSHA256; + + public static string HashPassword(string value) { + using var rng = RandomNumberGenerator.Create(); + var salt = new byte[SALT_SIZE]; + rng.GetBytes(salt); + var subkey = KeyDerivation.Pbkdf2(value, salt, PRF, ITERATION_COUNT, 256 / 8); + var outputBytes = new byte[13 + salt.Length + subkey.Length]; + WriteNetworkByteOrder(outputBytes, 1, (uint) PRF); + WriteNetworkByteOrder(outputBytes, 5, (uint) ITERATION_COUNT); + WriteNetworkByteOrder(outputBytes, 9, (uint) SALT_SIZE); + Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length); + Buffer.BlockCopy(subkey, 0, outputBytes, 13 + SALT_SIZE, subkey.Length); + return Convert.ToBase64String(outputBytes); + } + + public static bool Verify(string password, string hashedPassword) { + var decodedHashedPassword = Convert.FromBase64String(hashedPassword); + if (decodedHashedPassword.Length == 0) return false; + try { + // Read header information + var networkByteOrder = (KeyDerivationPrf) ReadNetworkByteOrder(decodedHashedPassword, 1); + var saltLength = (int) ReadNetworkByteOrder(decodedHashedPassword, 9); + + // Read the salt: must be >= 128 bits + if (saltLength < SALT_SIZE) return false; + var salt = new byte[saltLength]; + Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length); + + // Read the subkey (the rest of the payload): must be >= 128 bits + var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length; + if (subkeyLength < SALT_SIZE) return false; + var expectedSubkey = new byte[subkeyLength]; + Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); + + // Hash the incoming password and verify it + var actualSubkey = + KeyDerivation.Pbkdf2(password, salt, networkByteOrder, ITERATION_COUNT, subkeyLength); + return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey); + } catch { + return false; + } + } + + private static uint ReadNetworkByteOrder(IReadOnlyList buffer, int offset) { + return ((uint) buffer[offset + 0] << 24) + | ((uint) buffer[offset + 1] << 16) + | ((uint) buffer[offset + 2] << 8) + | buffer[offset + 3]; + } + + private static void WriteNetworkByteOrder(IList buffer, int offset, uint value) { + buffer[offset + 0] = (byte) (value >> 24); + buffer[offset + 1] = (byte) (value >> 16); + buffer[offset + 2] = (byte) (value >> 8); + buffer[offset + 3] = (byte) (value >> 0); + } + } +} \ No newline at end of file -- cgit v1.3