summaryrefslogtreecommitdiffstats
path: root/src/IOL.Helpers/PasswordHelpers.cs
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2021-04-01 22:36:59 +0200
committerivarlovlie <git@ivarlovlie.no>2021-04-01 22:36:59 +0200
commit9e0fbac6d3cb4e11efdb33c339ccf0adbca0bc6c (patch)
tree8fd1e8710abc3b17f0bc7f960c89931efd83c944 /src/IOL.Helpers/PasswordHelpers.cs
downloaddotnet-helpers-9e0fbac6d3cb4e11efdb33c339ccf0adbca0bc6c.tar.xz
dotnet-helpers-9e0fbac6d3cb4e11efdb33c339ccf0adbca0bc6c.zip
Initial commit
Diffstat (limited to 'src/IOL.Helpers/PasswordHelpers.cs')
-rw-r--r--src/IOL.Helpers/PasswordHelpers.cs70
1 files changed, 70 insertions, 0 deletions
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<byte> 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<byte> 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