1 \feff//Copyright (c) 2012 Josip Medved <jmedved@jmedved.com>
3 //2012-04-12: Initial version.
7 using System.Security.Cryptography;
10 namespace Medo.Security.Cryptography
14 /// Generic PBKDF2 implementation.
16 /// <example>This sample shows how to initialize class with SHA-256 HMAC.
18 /// using (var hmac = new HMACSHA256()) {
19 /// var df = new Pbkdf2(hmac, "password", "salt");
20 /// var bytes = df.GetBytes();
24 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pbkdf", Justification = "Spelling is correct.")]
29 /// Creates new instance.
31 /// <param name="algorithm">HMAC algorithm to use.</param>
32 /// <param name="password">The password used to derive the key.</param>
33 /// <param name="salt">The key salt used to derive the key.</param>
34 /// <param name="iterations">The number of iterations for the operation.</param>
35 /// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
36 public Pbkdf2(HMAC algorithm, Byte[] password, Byte[] salt, Int32 iterations)
38 if (algorithm == null) { throw new ArgumentNullException("algorithm", "Algorithm cannot be null."); }
39 if (salt == null) { throw new ArgumentNullException("salt", "Salt cannot be null."); }
40 if (password == null) { throw new ArgumentNullException("password", "Password cannot be null."); }
41 this.Algorithm = algorithm;
42 this.Algorithm.Key = password;
44 this.IterationCount = iterations;
45 this.BlockSize = this.Algorithm.HashSize / 8;
46 this.BufferBytes = new byte[this.BlockSize];
50 /// Creates new instance.
52 /// <param name="algorithm">HMAC algorithm to use.</param>
53 /// <param name="password">The password used to derive the key.</param>
54 /// <param name="salt">The key salt used to derive the key.</param>
55 /// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
56 public Pbkdf2(HMAC algorithm, Byte[] password, Byte[] salt)
57 : this(algorithm, password, salt, 1000)
62 /// Creates new instance.
64 /// <param name="algorithm">HMAC algorithm to use.</param>
65 /// <param name="password">The password used to derive the key.</param>
66 /// <param name="salt">The key salt used to derive the key.</param>
67 /// <param name="iterations">The number of iterations for the operation.</param>
68 /// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
69 public Pbkdf2(HMAC algorithm, String password, String salt, Int32 iterations) :
70 this(algorithm, UTF8Encoding.UTF8.GetBytes(password), UTF8Encoding.UTF8.GetBytes(salt), iterations)
75 /// Creates new instance.
77 /// <param name="algorithm">HMAC algorithm to use.</param>
78 /// <param name="password">The password used to derive the key.</param>
79 /// <param name="salt">The key salt used to derive the key.</param>
80 /// <exception cref="System.ArgumentNullException">Algorithm cannot be null - Password cannot be null. -or- Salt cannot be null.</exception>
81 public Pbkdf2(HMAC algorithm, String password, String salt) :
82 this(algorithm, password, salt, 1000)
87 private readonly int BlockSize;
88 private uint BlockIndex = 1;
90 private byte[] BufferBytes;
91 private int BufferStartIndex = 0;
92 private int BufferEndIndex = 0;
96 /// Gets algorithm used for generating key.
98 public HMAC Algorithm { get; private set; }
103 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Byte array is proper return value in this case.")]
104 public Byte[] Salt { get; private set; }
107 /// Gets iteration count.
109 public Int32 IterationCount { get; private set; }
113 /// Returns a pseudo-random key from a password, salt and iteration count.
115 /// <param name="count">Number of bytes to return.</param>
116 /// <returns>Byte array.</returns>
117 public Byte[] GetBytes(int count)
119 byte[] result = new byte[count];
120 int resultOffset = 0;
121 int bufferCount = this.BufferEndIndex - this.BufferStartIndex;
124 { //if there is some data in buffer
125 if (count < bufferCount)
126 { //if there is enough data in buffer
127 Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, count);
128 this.BufferStartIndex += count;
131 Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, bufferCount);
132 this.BufferStartIndex = this.BufferEndIndex = 0;
133 resultOffset += bufferCount;
136 while (resultOffset < count)
138 int needCount = count - resultOffset;
139 this.BufferBytes = this.Func();
140 if (needCount > this.BlockSize)
141 { //we one (or more) additional passes
142 Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, this.BlockSize);
143 resultOffset += this.BlockSize;
147 Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, needCount);
148 this.BufferStartIndex = needCount;
149 this.BufferEndIndex = this.BlockSize;
157 private byte[] Func()
159 var hash1Input = new byte[this.Salt.Length + 4];
160 Buffer.BlockCopy(this.Salt, 0, hash1Input, 0, this.Salt.Length);
161 Buffer.BlockCopy(GetBytesFromInt(this.BlockIndex), 0, hash1Input, this.Salt.Length, 4);
162 var hash1 = this.Algorithm.ComputeHash(hash1Input);
164 byte[] finalHash = hash1;
165 for (int i = 2; i <= this.IterationCount; i++)
167 hash1 = this.Algorithm.ComputeHash(hash1, 0, hash1.Length);
168 for (int j = 0; j < this.BlockSize; j++)
170 finalHash[j] = (byte)(finalHash[j] ^ hash1[j]);
173 if (this.BlockIndex == uint.MaxValue) { throw new InvalidOperationException("Derived key too long."); }
174 this.BlockIndex += 1;
179 private static byte[] GetBytesFromInt(uint i)
181 var bytes = BitConverter.GetBytes(i);
182 if (BitConverter.IsLittleEndian)
184 return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };