Skip to content

Instantly share code, notes, and snippets.

@trailmax
Created September 29, 2014 22:47
Show Gist options
  • Save trailmax/553ea84d4d0e2e20fcd7 to your computer and use it in GitHub Desktop.
Save trailmax/553ea84d4d0e2e20fcd7 to your computer and use it in GitHub Desktop.
Microsoft.AspNet.Identity.Crypto
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace Microsoft.AspNet.Identity
{
static class Crypto
{
private const int PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes
private const int PBKDF2SubkeyLength = 256/8; // 256 bits
private const int SaltSize = 128/8; // 128 bits
/* =======================
* HASHED PASSWORD FORMATS
* =======================
*
* Version 0:
* PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
* (See also: SDL crypto guidelines v5.1, Part III)
* Format: { 0x00, salt, subkey }
*/
public static string HashPassword(string password)
{
if (password == null)
{
throw new ArgumentNullException("password");
}
// Produce a version 0 (see comment above) text hash.
byte[] salt;
byte[] subkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount))
{
salt = deriveBytes.Salt;
subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
var outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength];
Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength);
return Convert.ToBase64String(outputBytes);
}
// hashedPassword must be of the format of HashWithPassword (salt + Hash(salt+input)
public static bool VerifyHashedPassword(string hashedPassword, string password)
{
if (hashedPassword == null)
{
return false;
}
if (password == null)
{
throw new ArgumentNullException("password");
}
var hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
// Verify a version 0 (see comment above) text hash.
if (hashedPasswordBytes.Length != (1 + SaltSize + PBKDF2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
{
// Wrong length or version header.
return false;
}
var salt = new byte[SaltSize];
Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, SaltSize);
var storedSubkey = new byte[PBKDF2SubkeyLength];
Buffer.BlockCopy(hashedPasswordBytes, 1 + SaltSize, storedSubkey, 0, PBKDF2SubkeyLength);
byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, PBKDF2IterCount))
{
generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
return ByteArraysEqual(storedSubkey, generatedSubkey);
}
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
[MethodImpl(MethodImplOptions.NoOptimization)]
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (ReferenceEquals(a, b))
{
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;
}
}
}
@jasonbrice
Copy link

Thanks, this was a critical resource in figuring out my problem (http://stackoverflow.com/questions/28706485/javascript-how-to-generate-rfc2898derivebytes-like-c)

@saskomateski
Copy link

Thanks, great job!

@kevingy
Copy link

kevingy commented Dec 3, 2023

Time has passed since this was posted. new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount)) (line 33) is now obsolete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment