Skip to content

Instantly share code, notes, and snippets.

@cwetanow
Last active December 15, 2017 10:22
Show Gist options
  • Save cwetanow/f8069ee0829f3a4a97fa7c0bf07b6d32 to your computer and use it in GitHub Desktop.
Save cwetanow/f8069ee0829f3a4a97fa7c0bf07b6d32 to your computer and use it in GitHub Desktop.
// Package Microsoft.AspNetCore.Cryptography.KeyDerivation
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace ConsoleApp1
{
public class PasswordHasher
{
private readonly int iterCount = 10000;
private readonly int saltSize = 128 / 8;
private readonly KeyDerivationPrf prf = KeyDerivationPrf.HMACSHA256;
private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (a == null && b == null)
{
return true;
}
if (a == null || b == null || a.Length != b.Length)
{
return false;
}
var areSame = true;
for (var i = 0; i < a.Length; i++)
{
areSame &= (a[i] == b[i]);
}
return areSame;
}
public string GenerateSalt()
{
byte[] salt = new byte[saltSize];
rng.GetBytes(salt);
return Convert.ToBase64String(salt);
}
public virtual string HashPassword(string password, string salt)
{
var saltBytes = Convert.FromBase64String(salt);
return Convert.ToBase64String(HashPasswordV3(password, saltBytes, 128 / 8, 256 / 8));
}
private byte[] HashPasswordV3(string password, byte[] salt, int saltSize, int numBytesRequested)
{
byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
var outputBytes = new byte[8 + subkey.Length];
WriteNetworkByteOrder(outputBytes, 0, (uint)prf);
WriteNetworkByteOrder(outputBytes, 4, (uint)iterCount);
Buffer.BlockCopy(subkey, 0, outputBytes, 8, subkey.Length);
return outputBytes;
}
private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
return ((uint)(buffer[offset + 0]) << 24)
| ((uint)(buffer[offset + 1]) << 16)
| ((uint)(buffer[offset + 2]) << 8)
| ((uint)(buffer[offset + 3]));
}
public virtual bool VerifyHashedPassword(string hashedPassword, string providedPassword, string salt)
{
byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword);
// read the format marker from the hashed password
var saltBytes = Convert.FromBase64String(salt);
return VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, saltBytes);
}
private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, byte[] salt)
{
var iterCount = default(int);
try
{
// Read header information
KeyDerivationPrf prf = (KeyDerivationPrf)ReadNetworkByteOrder(hashedPassword, 0);
iterCount = (int)ReadNetworkByteOrder(hashedPassword, 4);
// Read the subkey (the rest of the payload): must be >= 128 bits
int subkeyLength = hashedPassword.Length - 8;
byte[] expectedSubkey = new byte[subkeyLength];
Buffer.BlockCopy(hashedPassword, 8, expectedSubkey, 0, expectedSubkey.Length);
// Hash the incoming password and verify it
byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength);
return ByteArraysEqual(actualSubkey, expectedSubkey);
}
catch
{
// This should never occur except in the case of a malformed payload, where
// we might go off the end of the array. Regardless, a malformed payload
// implies verification failed.
return false;
}
}
private static void WriteNetworkByteOrder(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);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment