|
| 1 | +/* |
| 2 | + PBKDF2 key derivation |
| 3 | + implementation of PKCS#5 v2.0 |
| 4 | + Password Based Key Derivation Function 2 |
| 5 | + http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html |
| 6 | + For the HMAC function, see RFC 2104 |
| 7 | + http://www.ietf.org/rfc/rfc2104.txt |
| 8 | + Keith Brown |
| 9 | + |
| 10 | + Change log: |
| 11 | + 2004 Keith Brown - initial version |
| 12 | + 2012-09-01 Dror Gluska - change to SHA1 instead of SHA256 and add ability to pass the hashing function to the main function. |
| 13 | + |
| 14 | + |
| 15 | + */ |
| 16 | + |
| 17 | +using System; |
| 18 | +using System.Collections.Generic; |
| 19 | +using System.Linq; |
| 20 | +using System.Text; |
| 21 | +using System.Security.Cryptography; |
| 22 | + |
| 23 | +namespace HashingTest |
| 24 | +{ |
| 25 | + /// <summary> |
| 26 | + /// PBKDF2 Password-Based Key Derivation Function 2 |
| 27 | + /// </summary> |
| 28 | + class PBKDF2 |
| 29 | + { |
| 30 | + // SHA-256 has a 512-bit block size and gives a 256-bit output |
| 31 | + const int BLOCK_SIZE_IN_BYTES = 64; |
| 32 | + const byte IPAD = 0x36; |
| 33 | + const byte OPAD = 0x5C; |
| 34 | + |
| 35 | + public static byte[] SHA1GetBytes(byte[] password, byte[] salt, int iterations, int howManyBytes) |
| 36 | + { |
| 37 | + return GetBytes(new SHA1Managed(), password, salt, iterations, howManyBytes); |
| 38 | + } |
| 39 | + |
| 40 | + public static byte[] GetBytes(HashAlgorithm hashAlgo, byte[] password, |
| 41 | + byte[] salt, int iterations, int howManyBytes) |
| 42 | + { |
| 43 | + HashAlgorithm innerHash = hashAlgo; |
| 44 | + HashAlgorithm outerHash = hashAlgo; |
| 45 | + |
| 46 | + var HASH_SIZE_IN_BYTES = (innerHash.HashSize/8); |
| 47 | + |
| 48 | + // round up |
| 49 | + uint cBlocks = (uint)((howManyBytes + |
| 50 | + HASH_SIZE_IN_BYTES - 1) / HASH_SIZE_IN_BYTES); |
| 51 | + |
| 52 | + // seed for the pseudo-random fcn: salt + block index |
| 53 | + byte[] saltAndIndex = new byte[salt.Length + 4]; |
| 54 | + Array.Copy(salt, 0, saltAndIndex, 0, salt.Length); |
| 55 | + |
| 56 | + byte[] output = new byte[cBlocks * HASH_SIZE_IN_BYTES]; |
| 57 | + int outputOffset = 0; |
| 58 | + |
| 59 | + |
| 60 | + |
| 61 | + // HMAC says the key must be hashed or padded with zeros |
| 62 | + // so it fits into a single block of the hash in use |
| 63 | + if (password.Length > BLOCK_SIZE_IN_BYTES) |
| 64 | + { |
| 65 | + password = innerHash.ComputeHash(password); |
| 66 | + } |
| 67 | + byte[] key = new byte[BLOCK_SIZE_IN_BYTES]; |
| 68 | + Array.Copy(password, 0, key, 0, password.Length); |
| 69 | + |
| 70 | + byte[] InnerKey = new byte[BLOCK_SIZE_IN_BYTES]; |
| 71 | + byte[] OuterKey = new byte[BLOCK_SIZE_IN_BYTES]; |
| 72 | + for (int i = 0; i < BLOCK_SIZE_IN_BYTES; ++i) |
| 73 | + { |
| 74 | + InnerKey[i] = (byte)(key[i] ^ IPAD); |
| 75 | + OuterKey[i] = (byte)(key[i] ^ OPAD); |
| 76 | + } |
| 77 | + |
| 78 | + // for each block of desired output |
| 79 | + for (int iBlock = 0; iBlock < cBlocks; ++iBlock) |
| 80 | + { |
| 81 | + // seed HMAC with salt & block index |
| 82 | + _incrementBigEndianIndex(saltAndIndex, salt.Length); |
| 83 | + byte[] U = saltAndIndex; |
| 84 | + |
| 85 | + for (int i = 0; i < iterations; ++i) |
| 86 | + { |
| 87 | + // simple implementation of HMAC-SHA-256 |
| 88 | + innerHash.Initialize(); |
| 89 | + innerHash.TransformBlock(InnerKey, 0, |
| 90 | + BLOCK_SIZE_IN_BYTES, InnerKey, 0); |
| 91 | + innerHash.TransformFinalBlock(U, 0, U.Length); |
| 92 | + |
| 93 | + byte[] temp = innerHash.Hash; |
| 94 | + |
| 95 | + outerHash.Initialize(); |
| 96 | + outerHash.TransformBlock(OuterKey, 0, |
| 97 | + BLOCK_SIZE_IN_BYTES, OuterKey, 0); |
| 98 | + outerHash.TransformFinalBlock(temp, 0, temp.Length); |
| 99 | + |
| 100 | + U = outerHash.Hash; // U = result of HMAC |
| 101 | + |
| 102 | + // xor result into output buffer |
| 103 | + _xorByteArray(U, 0, HASH_SIZE_IN_BYTES, |
| 104 | + output, outputOffset); |
| 105 | + } |
| 106 | + outputOffset += HASH_SIZE_IN_BYTES; |
| 107 | + } |
| 108 | + byte[] result = new byte[howManyBytes]; |
| 109 | + Array.Copy(output, 0, result, 0, howManyBytes); |
| 110 | + return result; |
| 111 | + } |
| 112 | + // treat the four bytes starting at buf[offset] |
| 113 | + // as a big endian integer, and increment it |
| 114 | + static void _incrementBigEndianIndex(byte[] buf, int offset) |
| 115 | + { |
| 116 | + unchecked |
| 117 | + { |
| 118 | + if (0 == ++buf[offset + 3]) |
| 119 | + if (0 == ++buf[offset + 2]) |
| 120 | + if (0 == ++buf[offset + 1]) |
| 121 | + if (0 == ++buf[offset + 0]) |
| 122 | + throw new OverflowException(); |
| 123 | + } |
| 124 | + } |
| 125 | + static void _xorByteArray(byte[] src, |
| 126 | + int srcOffset, int cb, byte[] dest, int destOffset) |
| 127 | + { |
| 128 | + int end = checked(srcOffset + cb); |
| 129 | + while (srcOffset != end) |
| 130 | + { |
| 131 | + dest[destOffset] ^= src[srcOffset]; |
| 132 | + ++srcOffset; |
| 133 | + ++destOffset; |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | +} |
0 commit comments