Last active
December 15, 2017 10:22
-
-
Save cwetanow/f8069ee0829f3a4a97fa7c0bf07b6d32 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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