18155d4cad54f3da12910ec2e5caadd02fd262f6
[NovacoinLibrary.git] / Novacoin / PBKDF2.cs
1 \feff//Copyright (c) 2012 Josip Medved <jmedved@jmedved.com>
2
3 //2012-04-12: Initial version.
4
5
6 using System;
7 using System.Security.Cryptography;
8 using System.Text;
9
10 namespace Medo.Security.Cryptography
11 {
12
13     /// <summary>
14     /// Generic PBKDF2 implementation.
15     /// </summary>
16     /// <example>This sample shows how to initialize class with SHA-256 HMAC.
17     /// <code>
18     /// using (var hmac = new HMACSHA256()) {
19     ///     var df = new Pbkdf2(hmac, "password", "salt");
20     ///     var bytes = df.GetBytes();
21     /// }
22     /// </code>
23     /// </example>
24     [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pbkdf", Justification = "Spelling is correct.")]
25     public class Pbkdf2
26     {
27
28         /// <summary>
29         /// Creates new instance.
30         /// </summary>
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)
37         {
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;
43             this.Salt = salt;
44             this.IterationCount = iterations;
45             this.BlockSize = this.Algorithm.HashSize / 8;
46             this.BufferBytes = new byte[this.BlockSize];
47         }
48
49         /// <summary>
50         /// Creates new instance.
51         /// </summary>
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)
58         {
59         }
60
61         /// <summary>
62         /// Creates new instance.
63         /// </summary>
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)
71         {
72         }
73
74         /// <summary>
75         /// Creates new instance.
76         /// </summary>
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)
83         {
84         }
85
86
87         private readonly int BlockSize;
88         private uint BlockIndex = 1;
89
90         private byte[] BufferBytes;
91         private int BufferStartIndex = 0;
92         private int BufferEndIndex = 0;
93
94
95         /// <summary>
96         /// Gets algorithm used for generating key.
97         /// </summary>
98         public HMAC Algorithm { get; private set; }
99
100         /// <summary>
101         /// Gets salt bytes.
102         /// </summary>
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; }
105
106         /// <summary>
107         /// Gets iteration count.
108         /// </summary>
109         public Int32 IterationCount { get; private set; }
110
111
112         /// <summary>
113         /// Returns a pseudo-random key from a password, salt and iteration count.
114         /// </summary>
115         /// <param name="count">Number of bytes to return.</param>
116         /// <returns>Byte array.</returns>
117         public Byte[] GetBytes(int count)
118         {
119             byte[] result = new byte[count];
120             int resultOffset = 0;
121             int bufferCount = this.BufferEndIndex - this.BufferStartIndex;
122
123             if (bufferCount > 0)
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;
129                     return result;
130                 }
131                 Buffer.BlockCopy(this.BufferBytes, this.BufferStartIndex, result, 0, bufferCount);
132                 this.BufferStartIndex = this.BufferEndIndex = 0;
133                 resultOffset += bufferCount;
134             }
135
136             while (resultOffset < count)
137             {
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;
144                 }
145                 else
146                 {
147                     Buffer.BlockCopy(this.BufferBytes, 0, result, resultOffset, needCount);
148                     this.BufferStartIndex = needCount;
149                     this.BufferEndIndex = this.BlockSize;
150                     return result;
151                 }
152             }
153             return result;
154         }
155
156
157         private byte[] Func()
158         {
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);
163
164             byte[] finalHash = hash1;
165             for (int i = 2; i <= this.IterationCount; i++)
166             {
167                 hash1 = this.Algorithm.ComputeHash(hash1, 0, hash1.Length);
168                 for (int j = 0; j < this.BlockSize; j++)
169                 {
170                     finalHash[j] = (byte)(finalHash[j] ^ hash1[j]);
171                 }
172             }
173             if (this.BlockIndex == uint.MaxValue) { throw new InvalidOperationException("Derived key too long."); }
174             this.BlockIndex += 1;
175
176             return finalHash;
177         }
178
179         private static byte[] GetBytesFromInt(uint i)
180         {
181             var bytes = BitConverter.GetBytes(i);
182             if (BitConverter.IsLittleEndian)
183             {
184                 return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
185             }
186             else
187             {
188                 return bytes;
189             }
190         }
191
192     }
193 }