public void TestRfc2898DeriveBytes() { byte[] password = new byte[20]; byte[] salt = new byte[20]; int bytes = 64; for (int iterations = 1000; iterations <= 10000; iterations += 1000) { Tester.RandomGenerator.NextBytes(password); Tester.RandomGenerator.NextBytes(salt); using (PBKDF2<HMACSHA1> pbkdf2 = new PBKDF2<HMACSHA1>(password, salt, iterations)) using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(password, salt, iterations)) { byte[] pbkdf2Bytes = pbkdf2.GetBytes(bytes); byte[] rfc2898Bytes = rfc2898.GetBytes(bytes); Assert.AreEqual(bytes, rfc2898Bytes.Length); Assert.AreEqual(bytes, pbkdf2Bytes.Length); for (int i = 0; i < bytes; i++) Assert.AreEqual(rfc2898Bytes[i], pbkdf2Bytes[i]); } } }
public static void TestPBKDF2() { PBKDF2 kg; byte[] DK; byte[] tv1 = new byte[] { 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06, 0x2f, 0xe0, 0x37, 0xa6 }; kg = new PBKDF2(Encoding.Default.GetBytes("password"), Encoding.Default.GetBytes("salt"), 1); DK = kg.GetBytes(20); Assert.AreEqual(DK, tv1); byte[] tv2 = new byte[] { 0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0, 0xd8, 0xde, 0x89, 0x57 }; kg = new PBKDF2(Encoding.Default.GetBytes("password"), Encoding.Default.GetBytes("salt"), 2); DK = kg.GetBytes(20); Assert.AreEqual(DK, tv2); byte[] tv3 = new byte[] { 0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a, 0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0, 0x65, 0xa4, 0x29, 0xc1 }; kg = new PBKDF2(Encoding.Default.GetBytes("password"), Encoding.Default.GetBytes("salt"), 4096); DK = kg.GetBytes(20); Assert.AreEqual(DK, tv3); byte[] tv4 = new byte[] { 0x2f, 0x25, 0x5b, 0x3a, 0x95, 0x46, 0x3c, 0x76, 0x62, 0x1f, 0x06, 0x80, 0xa2, 0xb3, 0x35, 0xad, 0x90, 0x3b, 0x85, 0xde }; kg = new PBKDF2(Encoding.Default.GetBytes("VXFr[24c=6(D8He"), Encoding.Default.GetBytes("salt"), 1000); DK = kg.GetBytes(20); Assert.AreEqual(DK, tv4); }
public string Hash(string password, string userSalt) { using (SHA512 alg = SHA512.Create()) { // _hashArraySize is always a power of 2 int bitmask = _memoryBlocks - 1; // we have a single running hash, but then we combine it with "randomly"-selected cells from the array var combinedHash = new byte[(MULTIPLIER + 1) * PasswordHasher.DigestSize]; /* add one because we'll store the running hash there */ #if DEBUG _hashes = new List<string>(); _visitCounts = new int[_memoryBlocks]; _hashesPerCell = new List<string>[_memoryBlocks]; _hashToCells = new Dictionary<string, int[]>(); for (int i = 0; i < _hashesPerCell.Length; i++ ) _hashesPerCell[i] = new List<string>(); #endif byte[] hash = new byte[PasswordHasher.DigestSize]; // the running hash // PBKDF2-HMAC-SHA512 the user's password with (system salt + user salt) and use it to fill _hashArray var pbkdf2 = new PBKDF2<HMACSHA512>(Encoding.ASCII.GetBytes(password), Encoding.ASCII.GetBytes(_systemSalt + userSalt ?? ""), 1); var bytes = pbkdf2.GetBytes(_fillAmount + PasswordHasher.DigestSize); /* initialize the running hash to what comes out of PBKDF2 after the _hashArray is filled */ Buffer.BlockCopy(bytes, _fillAmount, hash, 0, PasswordHasher.DigestSize); int blockStart = 0; int[] blockStarts = new int[MULTIPLIER]; // We only PBKDF2 enough for 1/16th of the _hashArray, so duplicate that "_fillAmount" 16 times for (var i = 0; i < MULTIPLIER; i++) { Buffer.BlockCopy(bytes, 0, _hashArray, blockStart, _fillAmount); blockStart += _fillAmount; } #if DEBUG _originalArray = new byte[_hashArray.Length]; Buffer.BlockCopy(_hashArray, 0, _originalArray, 0, _hashArray.Length); #endif // now "randomly mix up" the hash array for (int i = 0; i < _mixingIterations; i++) { #if DEBUG AddHash(hash); #endif int combinedHashEnd = PasswordHasher.DigestSize; // combine the running hash with... Buffer.BlockCopy(hash, 0, combinedHash, 0, PasswordHasher.DigestSize); // ..."randomly"-selected cells in the hash array for (int m = 0; m < MULTIPLIER; m++ ) { // create a random int from bytes in the running hash and interpret the int as which cell to get a hash from. // Since hashes are 64 bytes long and ints are 4 bytes, we can only get 16 random indexes from the hash, // which is why MULTIPLIER is 16. int nextIndex = (hash[m * 4] + (hash[m * 4 + 1] << 8) + (hash[m * 4 + 2] << 16) + (hash[m * 4 + 3] << 24)) & bitmask; // add that selected hash to the combined hash blockStarts[m] = blockStart = nextIndex * PasswordHasher.DigestSize; Buffer.BlockCopy(_hashArray, blockStart, combinedHash, combinedHashEnd, PasswordHasher.DigestSize); combinedHashEnd += PasswordHasher.DigestSize; #if DEBUG _visitCounts[nextIndex]++; #endif } // update the running hash hash = alg.ComputeHash(combinedHash); #if DEBUG var base64hash = Convert.ToBase64String(hash); _hashToCells[base64hash] = new int[MULTIPLIER]; #endif for (int m = 0; m < MULTIPLIER; m++) { blockStart = blockStarts[m]; // xor the selected hash with the running hash so that the hash array is constantly being modified for (int b = 0; b < PasswordHasher.DigestSize; b++) _hashArray[blockStart + b] ^= hash[b]; #if DEBUG int hashIndex = blockStart / PasswordHasher.DigestSize; _hashesPerCell[hashIndex].Add(base64hash); _hashToCells[base64hash][m] = hashIndex; #endif } } return Convert.ToBase64String(alg.ComputeHash(_hashArray, 0, MemoryUsage)); } }