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]); } } }
// the core function of the PBKDF which does all the iterations // per the spec section 5.2 step 3 private byte[] _F(uint I) { //NOTE: SPEC IS MISLEADING!!! //THE HMAC FUNCTIONS ARE KEYED BY THE PASSWORD! NEVER THE SALT! byte[] bufferU = null; byte[] bufferOut = null; byte[] _int = PBKDF2 <T> .IntToBytes(I); _hmac = new T(); _hmac.Key = (_P); // KEY BY THE PASSWORD! _hmac.TransformBlock(_S, 0, _S.Length, _S, 0); _hmac.TransformFinalBlock(_int, 0, _int.Length); bufferU = _hmac.Hash; bufferOut = (byte[])bufferU.Clone(); for (int c = 1; c < _C; c++) { _hmac.Initialize(); _hmac.Key = _P; // KEY BY THE PASSWORD! bufferU = _hmac.ComputeHash(bufferU); _Xor(ref bufferOut, bufferU); } return(bufferOut); }
public void PBKDF2_SHA512() { // tests with salt less than 8 bytes are skipped since our implementation throws on such weak salts. var result = new PBKDF2(HMACFactories.HMACSHA512, password: Encoding.ASCII.GetBytes("passwordPASSWORDpassword"), salt: Encoding.ASCII.GetBytes("saltSALTsaltSALTsaltSALTsaltSALTsalt"), iterations: 4096).GetBytes(64); var expected = "8c 05 11 f4 c6 e5 97 c6 ac 63 15 d8 f0 36 2e 22 5f 3c 50 14 95 ba 23 b8 68 c0 05 17 4d c4 ee 71 11 5b 59 f9 e6 0c d9 53 2f a3 3e 0f 75 ae fe 30 22 5c 58 3a 18 6c d8 2b d4 da ea 97 24 a3 d3 b8".Replace(" ", "").FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); }
public void PBKDF2_SHA256() { // tests with salt less than 8 bytes are skipped since our implementation throws on such weak salts. var result = new PBKDF2(HMACFactories.HMACSHA256, password: Encoding.ASCII.GetBytes("passwordPASSWORDpassword"), salt: Encoding.ASCII.GetBytes("saltSALTsaltSALTsaltSALTsaltSALTsalt"), iterations: 4096).GetBytes(40); var expected = "34 8c 89 db cb d3 2b 2f 32 d8 14 b8 11 6e 84 cf 2b 17 34 7e bc 18 00 18 1c 4e 2a 1f b8 dd 53 e1 c6 35 51 8c 7d ac 47 e9".Replace(" ", "").FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); }
public void PBKDF2_SHA1() { // tests with salt less than 8 bytes are skipped since our implementation throws on such weak salts. var result = new PBKDF2(HMACFactories.HMACSHA1, password: Encoding.ASCII.GetBytes("passwordPASSWORDpassword"), salt: Encoding.ASCII.GetBytes("saltSALTsaltSALTsaltSALTsaltSALTsalt"), iterations: 4096).GetBytes(25); var expected = "3d 2e ec 4f e4 1c 84 9b 80 c8 d8 36 62 c0 e4 4a 8b 29 1a 96 4c f2 f0 70 38".Replace(" ", "").FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); }
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); }
/// <summary> /// Decrypt a hex-coded string using our MD5 or PBKDF2 generated key /// </summary> /// <param name="data">data string to be decrypted</param> /// <param name="key">decryption key</param> /// <param name="PBKDF2">flag to indicate we are using PBKDF2 to generate derived key</param> /// <returns>hex coded decrypted string</returns> public static string Decrypt(string data, string password, bool PBKDF2) { byte[] key; byte[] saltBytes = Authenticator.StringToByteArray(data.Substring(0, SALT_LENGTH * 2)); if (PBKDF2 == true) { // extract the salt from the data byte[] passwordBytes = Encoding.UTF8.GetBytes(password); // build our PBKDF2 key #if NETCF PBKDF2 kg = new PBKDF2(passwordBytes, saltbytes, 2000); #else Rfc2898DeriveBytes kg = new Rfc2898DeriveBytes(passwordBytes, saltBytes, PBKDF2_ITERATIONS); #endif key = kg.GetBytes(PBKDF2_KEYSIZE); } else { // extract the salt from the data byte[] passwordBytes = Encoding.UTF8.GetBytes(password); key = new byte[saltBytes.Length + passwordBytes.Length]; Array.Copy(saltBytes, key, saltBytes.Length); Array.Copy(passwordBytes, 0, key, saltBytes.Length, passwordBytes.Length); // build out combined key SHA256Managed md5 =new SHA256Managed(); key = md5.ComputeHash(key); } // extract the actual data to be decrypted byte[] inBytes = Authenticator.StringToByteArray(data.Substring(SALT_LENGTH * 2)); // get cipher BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new ISO10126d2Padding()); cipher.Init(false, new KeyParameter(key)); // decrypt the data int osize = cipher.GetOutputSize(inBytes.Length); byte[] outBytes = new byte[osize]; try { int olen = cipher.ProcessBytes(inBytes, 0, inBytes.Length, outBytes, 0); olen += cipher.DoFinal(outBytes, olen); if (olen < osize) { byte[] t = new byte[olen]; Array.Copy(outBytes, 0, t, 0, olen); outBytes = t; } } catch (Exception) { // an exception is due to bad password throw new BadPasswordException(); } // return encoded string return Authenticator.ByteArrayToString(outBytes); }
/// <summary> /// Encrypt a string with a given key /// </summary> /// <param name="plain">data to encrypt - hex representation of byte array</param> /// <param name="key">key to use to encrypt</param> /// <returns>hex coded encrypted string</returns> public static string Encrypt(string plain, string password) { byte[] inBytes = Authenticator.StringToByteArray(plain); byte[] passwordBytes = Encoding.UTF8.GetBytes(password); // build a new salt RNGCryptoServiceProvider rg = new RNGCryptoServiceProvider(); byte[] saltbytes = new byte[SALT_LENGTH]; rg.GetBytes(saltbytes); string salt = Authenticator.ByteArrayToString(saltbytes); // build our PBKDF2 key #if NETCF PBKDF2 kg = new PBKDF2(passwordBytes, saltbytes, PBKDF2_ITERATIONS); #else Rfc2898DeriveBytes kg = new Rfc2898DeriveBytes(passwordBytes, saltbytes, PBKDF2_ITERATIONS); #endif byte[] key = kg.GetBytes(PBKDF2_KEYSIZE); // get our cipher BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new BlowfishEngine(), new ISO10126d2Padding()); cipher.Init(true, new KeyParameter(key)); // encrypt data int osize = cipher.GetOutputSize(inBytes.Length); byte[] outBytes = new byte[osize]; int olen = cipher.ProcessBytes(inBytes, 0, inBytes.Length, outBytes, 0); olen += cipher.DoFinal(outBytes, olen); if (olen < osize) { byte[] t = new byte[olen]; Array.Copy(outBytes, 0, t, 0, olen); outBytes = t; } // return encoded byte->hex string return salt + Authenticator.ByteArrayToString(outBytes); }
/// <summary> /// Encrypt a string with a given key /// </summary> /// <param name="plain">data to encrypt - hex representation of byte array</param> /// <param name="password">key to use to encrypt</param> /// <returns>hex coded encrypted string</returns> public static string Encrypt(string plain, string password) { byte[] passwordBytes = Encoding.UTF8.GetBytes(password); // build a new salt RNGCryptoServiceProvider rg = new RNGCryptoServiceProvider(); byte[] saltbytes = new byte[SALT_LENGTH]; rg.GetBytes(saltbytes); string salt = Authenticator.ByteArrayToString(saltbytes); // build our PBKDF2 key #if NETCF PBKDF2 kg = new PBKDF2(passwordBytes, saltbytes, PBKDF2_ITERATIONS); #else Rfc2898DeriveBytes kg = new Rfc2898DeriveBytes(passwordBytes, saltbytes, PBKDF2_ITERATIONS); #endif byte[] key = kg.GetBytes(PBKDF2_KEYSIZE); return salt + Encrypt(plain, key); }
public void PBKDF2_SHA512() { // tests with salt less than 8 bytes are skipped since our implementation throws on such weak salts. var hmacFactories = new Func<HMAC>[] { () => new HMACSHA512(), HMACFactories.HMACSHA512 }; foreach (var hmacFactory in hmacFactories) { var result = new PBKDF2(hmacFactory, password: Encoding.ASCII.GetBytes("passwordPASSWORDpassword"), salt: Encoding.ASCII.GetBytes("saltSALTsaltSALTsaltSALTsaltSALTsalt"), iterations: 4096).GetBytes(64); var expected = "8c 05 11 f4 c6 e5 97 c6 ac 63 15 d8 f0 36 2e 22 5f 3c 50 14 95 ba 23 b8 68 c0 05 17 4d c4 ee 71 11 5b 59 f9 e6 0c d9 53 2f a3 3e 0f 75 ae fe 30 22 5c 58 3a 18 6c d8 2b d4 da ea 97 24 a3 d3 b8".Replace(" ", "").FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); result = new PBKDF2(hmacFactory, password: Encoding.ASCII.GetBytes("passDATAb00AB7YxDTTlRH2dqxDx19GDxDV1zFMz7E6QVqKIzwOtMnlxQLttpE57Un4u12D2YD7oOPpiEvCDYvntXEe4NNPLCnGGeJArbYDEu6xDoCfWH6kbuV6awi04Uz3ebEAhzZ4ve1A2wg5CnLXdZC5Y7gwfVgbEgZSTmoYQSzC5OW4dfrjqiwApTACO6xoOL1AjWj6X6f6qFfF8TVmOzU9RhOd1N4QtzWI4fP6FYttNz5FuLdtYVXWVXH2Tf7I9fieMeWCHTMkM4VcmQyQHpbcP8MEb5f1g6Ckg5xk3HQr3wMBvQcOHpCPy1K8HCM7a5wkPDhgVA0BVmwNpsRIbDQZRtHK6dT6bGyalp6gbFZBuBHwD86gTzkrFY7HkOVrgc0gJcGJZe65Ce8v4Jn5OzkuVsiU8efm2Pw2RnbpWSAr7SkVdCwXK2XSJDQ5fZ4HBEz9VTFYrG23ELuLjvx5njOLNgDAJuf5JB2tn4nMjjcnl1e8qcYVwZqFzEv2zhLyDWMkV4tzl4asLnvyAxTBkxPRZj2pRABWwb3kEofpsHYxMTAn38YSpZreoXipZWBnu6HDURaruXaIPYFPYHl9Ls9wsuD7rzaGfbOyfVgLIGK5rODphwRA7lm88bGKY8b7tWOtepyEvaLxMI7GZF5ScwpZTYeEDNUKPzvM2Im9zehIaznpguNdNXNMLWnwPu4H6zEvajkw3G3ucSiXKmh6XNe3hkdSANm3vnxzRXm4fcuzAx68IElXE2bkGFElluDLo6EsUDWZ4JIWBVaDwYdJx8uCXbQdoifzCs5kuuClaDaDqIhb5hJ2WR8mxiueFsS0aDGdIYmye5svmNmzQxFmdOkHoF7CfwuU1yy4uEEt9vPSP2wFp1dyaMvJW68vtB4kddLmI6gIgVVcT6ZX1Qm6WsusPrdisPLB2ScodXojCbL3DLj6PKG8QDVMWTrL1TpafT2wslRledWIhsTlv2mI3C066WMcTSwKLXdEDhVvFJ6ShiLKSN7gnRrlE0BnAw"), salt: Encoding.ASCII.GetBytes("saltKEYbcTcXHCBxtjD2PnBh44AIQ6XUOCESOhXpEp3HrcGMwbjzQKMSaf63IJemkURWoqHusIeVB8Il91NjiCGQacPUu9qTFaShLbKG0Yj4RCMV56WPj7E14EMpbxy6PlBdILBOkKUB6TGTPJXh1tpdOHTG6KuIvcbQp9qWjaf1uxAKgiTtYRIHhxjJI2viVa6fDZ67QOouOaf2RXQhpsWaTtAVnff6PIFcvJhdPDFGV5nvmZWoCZQodj6yXRDHPw9PyF0iLYm9uFtEunlAAxGB5qqea4X5tZvB1OfLVwymY3a3JPjdxTdvHxCHbqqE0zip61JNqdmeWxGtlRBC6CGoCiHO4XxHCntQBRJDcG0zW7joTdgtTBarsQQhlLXBGMNBSNmmTbDf3hFtawUBCJH18IAiRMwyeQJbJ2bERsY3MVRPuYCf4Au7gN72iGh1lRktSQtEFye7pO46kMXRrEjHQWXInMzzy7X2StXUzHVTFF2VdOoKn0WUqFNvB6PF7qIsOlYKj57bi1Psa34s85WxMSbTkhrd7VHdHZkTVaWdraohXYOePdeEvIwObCGEXkETUzqM5P2yzoBOJSdjpIYaa8zzdLD3yrb1TwCZuJVxsrq0XXY6vErU4QntsW0972XmGNyumFNJiPm4ONKh1RLvS1kddY3nm8276S4TUuZfrRQO8QxZRNuSaZI8JRZp5VojB5DktuMxAQkqoPjQ5Vtb6oXeOyY591CB1MEW1fLTCs0NrL321SaNRMqza1ETogAxpEiYwZ6pIgnMmSqNMRdZnCqA4gMWw1lIVATWK83OCeicNRUNOdfzS7A8vbLcmvKPtpOFvhNzwrrUdkvuKvaYJviQgeR7snGetO9JLCwIlHIj52gMCNU18d32SJl7Xomtl3wIe02SMvq1i1BcaX7lXioqWGmgVqBWU3fsUuGwHi6RUKCCQdEOBfNo2WdpFaCflcgnn0O6jVHCqkv8cQk81AqS00rAmHGCNTwyA6Tq5TXoLlDnC8gAQjDUsZp0z"), iterations: 100000).GetBytes(64 + 1); expected = "B8674F6C0CC9F8CF1F1874534FD5AF01FC1504D76C2BC2AA0A75FE4DD5DFD1DAF60EA7C85F122BCEEB8772659D601231607726998EAC3F6AAB72EFF7BA349F7FD7".FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); } }
public void PBKDF2_SHA384() { byte[] result, expected; var hmacFactories = new Func<HMAC>[] { () => new HMACSHA384(), HMACFactories.HMACSHA384 }; foreach (var hmacFactory in hmacFactories) { result = new PBKDF2(hmacFactory, password: Encoding.ASCII.GetBytes("passDATAb00AB7YxDTTlRH2dqxDx19GDxDV1zFMz7E6QVqK"), salt: Encoding.ASCII.GetBytes("saltKEYbcTcXHCBxtjD2PnBh44AIQ6XUOCESOhXpEp3HrcG"), iterations: 1).GetBytes(48); expected = "0644A3489B088AD85A0E42BE3E7F82500EC18936699151A2C90497151BAC7BB69300386A5E798795BE3CEF0A3C803227".FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); result = new PBKDF2(hmacFactory, password: Encoding.ASCII.GetBytes("passDATAb00AB7YxDTTlRH2dqxDx19GDxDV1zFMz7E6QVqKIzwOtMnlxQLttpE57Un4u12D2YD7oOPpiEvCDYvntXEe4NNPLCnGGeJArbYDEu6xDoCfWH6kbuV6awi04Uz3ebEAhzZ4ve1A2wg5CnLXdZC5Y7gwfVgbEgZSTmoYQSzC5OW4dfrjqiwApTACO6xoOL1AjWj6X6f6qFfF8TVmOzU9RhOd1N4QtzWI4fP6FYttNz5FuLdtYVXWVXH2Tf7I9fieMeWCHTMkM4VcmQyQHpbcP8MEb5f1g6Ckg5xk3HQr3wMBvQcOHpCPy1K8HCM7a5wkPDhgVA0BVmwNpsRIbDQZRtHK6dT6bGyalp6gbFZBuBHwD86gTzkrFY7HkOVrgc0gJcGJZe65Ce8v4Jn5OzkuVsiU8efm2Pw2RnbpWSAr7SkVdCwXK2XSJDQ5fZ4HBEz9VTFYrG23ELuLjvx5njOLNgDAJuf5JB2tn4nMjjcnl1e8qcYVwZqFzEv2zhLyDWMkV4tzl4asLnvyAxTBkxPRZj2pRABWwb3kEofpsHYxMTAn38YSpZreoXipZWBnu6HDURaruXaIPYFPYHl9Ls9wsuD7rzaGfbOyfVgLIGK5rODphwRA7lm88bGKY8b7tWOtepyEvaLxMI7GZF5ScwpZTYeEDNUKPzvM2Im9zehIaznpguNdNXNMLWnwPu4H6zEvajkw3G3ucSiXKmh6XNe3hkdSANm3vnxzRXm4fcuzAx68IElXE2bkGFElluDLo6EsUDWZ4JIWBVaDwYdJx8uCXbQdoifzCs5kuuClaDaDqIhb5hJ2WR8mxiueFsS0aDGdIYmye5svmNmzQxFmdOkHoF7CfwuU1yy4uEEt9vPSP2wFp1dyaMvJW68vtB4kddLmI6gIgVVcT6ZX1Qm6WsusPrdisPLB2ScodXojCbL3DLj6PKG8QDVMWTrL1TpafT2wslRledWIhsTlv2mI3C066WMcTSwKLXdEDhVvFJ6ShiLKSN7gnRrlE0BnAw"), salt: Encoding.ASCII.GetBytes("saltKEYbcTcXHCBxtjD2PnBh44AIQ6XUOCESOhXpEp3HrcGMwbjzQKMSaf63IJemkURWoqHusIeVB8Il91NjiCGQacPUu9qTFaShLbKG0Yj4RCMV56WPj7E14EMpbxy6PlBdILBOkKUB6TGTPJXh1tpdOHTG6KuIvcbQp9qWjaf1uxAKgiTtYRIHhxjJI2viVa6fDZ67QOouOaf2RXQhpsWaTtAVnff6PIFcvJhdPDFGV5nvmZWoCZQodj6yXRDHPw9PyF0iLYm9uFtEunlAAxGB5qqea4X5tZvB1OfLVwymY3a3JPjdxTdvHxCHbqqE0zip61JNqdmeWxGtlRBC6CGoCiHO4XxHCntQBRJDcG0zW7joTdgtTBarsQQhlLXBGMNBSNmmTbDf3hFtawUBCJH18IAiRMwyeQJbJ2bERsY3MVRPuYCf4Au7gN72iGh1lRktSQtEFye7pO46kMXRrEjHQWXInMzzy7X2StXUzHVTFF2VdOoKn0WUqFNvB6PF7qIsOlYKj57bi1Psa34s85WxMSbTkhrd7VHdHZkTVaWdraohXYOePdeEvIwObCGEXkETUzqM5P2yzoBOJSdjpIYaa8zzdLD3yrb1TwCZuJVxsrq0XXY6vErU4QntsW0972XmGNyumFNJiPm4ONKh1RLvS1kddY3nm8276S4TUuZfrRQO8QxZRNuSaZI8JRZp5VojB5DktuMxAQkqoPjQ5Vtb6oXeOyY591CB1MEW1fLTCs0NrL321SaNRMqza1ETogAxpEiYwZ6pIgnMmSqNMRdZnCqA4gMWw1lIVATWK83OCeicNRUNOdfzS7A8vbLcmvKPtpOFvhNzwrrUdkvuKvaYJviQgeR7snGetO9JLCwIlHIj52gMCNU18d32SJl7Xomtl3wIe02SMvq1i1BcaX7lXioqWGmgVqBWU3fsUuGwHi6RUKCCQdEOBfNo2WdpFaCflcgnn0O6jVHCqkv8cQk81AqS00rAmHGCNTwyA6Tq5TXoLlDnC8gAQjDUsZp0z"), iterations: 100000).GetBytes(48 + 1); expected = "7BADBDA9DBE9D5AB9237268D57ABB235B6B729AEFA9CACDF5E3007136F1178231FCFFE3E6437D9EF713EC32887C4B42674".FromBase16(); Assert.IsTrue(Enumerable.SequenceEqual(expected, result)); } }
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)); } }