Files

104 lines
2.9 KiB
C#

using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace MileageTraker.Web.Utility
{
public static class Crypto
{
private const int Pbkdf2Count = 1000;
private const int Pbkdf2SubkeyLength = 256/8;
private const int SaltSize = 128/8;
/* =======================
* 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");
byte[] salt;
byte[] subkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, Pbkdf2Count))
{
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)
throw new ArgumentNullException("hashedPassword");
if (password == null)
throw new ArgumentNullException("password");
byte[] hashedPasswordBytes;
try
{
hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
}
catch (FormatException)
{
return false;
}
// Verify a version 0 (see comment above) password 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, Pbkdf2Count))
{
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;
}
bool areSame = true;
for (int i = 0; i < a.Length; i++)
{
areSame &= (a[i] == b[i]);
}
return areSame;
}
}
}