ae1bb1f8b53ff2fa53f90cd715524ef76712dc66
[mono.git] / mcs / class / corlib / System.Security.Cryptography / PasswordDeriveBytes.cs
1 //
2 // PasswordDeriveBytes.cs: Handles PKCS#5 key derivation using password
3 //
4 // Author:
5 //      Sebastien Pouliot (sebastien@ximian.com)
6 //
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004-2007 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System.Globalization;
31 using System.Runtime.InteropServices;
32 using System.Text;
33
34 namespace System.Security.Cryptography {
35
36 // References:
37 // a.   PKCS #5 - Password-Based Cryptography Standard 
38 //      http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html
39 // b.   IETF RFC2898: PKCS #5: Password-Based Cryptography Specification Version 2.0
40 //      http://www.rfc-editor.org/rfc/rfc2898.txt
41
42 #if NET_2_0
43 [ComVisible (true)]
44 #endif
45 public class PasswordDeriveBytes : DeriveBytes {
46
47         private string HashNameValue;
48         private byte[] SaltValue;
49         private int IterationsValue;
50
51         private HashAlgorithm hash;
52         private int state;
53         private byte[] password;
54         private byte[] initial;
55         private byte[] output;
56         private int position;
57         private int hashnumber;
58
59         public PasswordDeriveBytes (string strPassword, byte[] rgbSalt) 
60         {
61                 Prepare (strPassword, rgbSalt, "SHA1", 100);
62         }
63
64         public PasswordDeriveBytes (string strPassword, byte[] rgbSalt, CspParameters cspParams) 
65         {
66                 Prepare (strPassword, rgbSalt, "SHA1", 100);
67                 if (cspParams != null) {
68                         throw new NotSupportedException (
69                                 Locale.GetText ("CspParameters not supported by Mono for PasswordDeriveBytes."));
70                 }
71         }
72
73         public PasswordDeriveBytes (string strPassword, byte[] rgbSalt, string strHashName, int iterations) 
74         {
75                 Prepare (strPassword, rgbSalt, strHashName, iterations);
76         }
77
78         public PasswordDeriveBytes (string strPassword, byte[] rgbSalt, string strHashName, int iterations, CspParameters cspParams) 
79         {
80                 Prepare (strPassword, rgbSalt, strHashName, iterations);
81                 if (cspParams != null) {
82                         throw new NotSupportedException (
83                                 Locale.GetText ("CspParameters not supported by Mono for PasswordDeriveBytes."));
84                 }
85         }
86
87 #if NET_2_0
88         public PasswordDeriveBytes (byte[] password, byte[] salt) 
89         {
90                 Prepare (password, salt, "SHA1", 100);
91         }
92
93         public PasswordDeriveBytes (byte[] password, byte[] salt, CspParameters cspParams) 
94         {
95                 Prepare (password, salt, "SHA1", 100);
96                 if (cspParams != null) {
97                         throw new NotSupportedException (
98                                 Locale.GetText ("CspParameters not supported by Mono for PasswordDeriveBytes."));
99                 }
100         }
101
102         public PasswordDeriveBytes (byte[] password, byte[] salt, string hashName, int iterations) 
103         {
104                 Prepare (password, salt, hashName, iterations);
105         }
106
107         public PasswordDeriveBytes (byte[] password, byte[] salt, string hashName, int iterations, CspParameters cspParams) 
108         {
109                 Prepare (password, salt, hashName, iterations);
110                 if (cspParams != null) {
111                         throw new NotSupportedException (
112                                 Locale.GetText ("CspParameters not supported by Mono for PasswordDeriveBytes."));
113                 }
114         }
115 #endif
116
117         ~PasswordDeriveBytes () 
118         {
119                 // zeroize buffer
120                 if (initial != null) {
121                         Array.Clear (initial, 0, initial.Length);
122                         initial = null;
123                 }
124                 // zeroize temporary password storage
125                 Array.Clear (password, 0, password.Length);
126         }
127
128 #if NET_2_0
129         private void Prepare (string strPassword, byte[] rgbSalt, string strHashName, int iterations) 
130         {
131                 if (strPassword == null)
132                         throw new ArgumentNullException ("strPassword");
133
134                 byte[] pwd = Encoding.UTF8.GetBytes (strPassword);
135                 Prepare (pwd, rgbSalt, strHashName, iterations);
136                 Array.Clear (pwd, 0, pwd.Length);
137         }
138
139         private void Prepare (byte[] password, byte[] rgbSalt, string strHashName, int iterations)
140         {
141                 if (password == null)
142                         throw new ArgumentNullException ("password");
143
144                 this.password = (byte[]) password.Clone ();
145
146                 Salt = rgbSalt;
147
148                 HashName = strHashName;
149                 IterationCount = iterations;
150                 state = 0;
151         }
152 #else
153         private void Prepare (string strPassword, byte[] rgbSalt, string strHashName, int iterations)
154         {
155                 if (strPassword == null)
156                         password = null;
157                 else
158                         password = Encoding.UTF8.GetBytes (strPassword);
159
160                 if (rgbSalt == null)
161                         SaltValue = null;
162                 else
163                         SaltValue = (byte[]) rgbSalt.Clone ();
164
165                 HashName = strHashName;
166                 IterationCount = iterations;
167                 state = 0;
168         }
169 #endif
170
171         public string HashName {
172                 get { return HashNameValue; } 
173                 set {
174                         if (value == null)
175                                 throw new ArgumentNullException ("HashName");
176                         if (state != 0) {
177                                 throw new CryptographicException (
178                                         Locale.GetText ("Can't change this property at this stage"));
179                         }
180                         HashNameValue = value;
181                 }
182         }
183
184         public int IterationCount {
185                 get { return IterationsValue; }
186                 set {
187                         if (value < 1)
188                                 throw new ArgumentOutOfRangeException ("> 0", "IterationCount");
189                         if (state != 0) {
190                                 throw new CryptographicException (
191                                         Locale.GetText ("Can't change this property at this stage"));
192                         }
193                         IterationsValue = value;
194                 }
195         }
196
197         public byte[] Salt {
198                 get { 
199                         if (SaltValue == null)
200                                 return null;
201                         return (byte[]) SaltValue.Clone ();
202                 }
203                 set {
204                         if (state != 0) {
205                                 throw new CryptographicException (
206                                         Locale.GetText ("Can't change this property at this stage"));
207                         }
208 #if NET_2_0
209                         if (value != null)
210                                 SaltValue = (byte[]) value.Clone ();
211                         else
212                                 SaltValue = null;
213 #else
214                         // this will cause a NullReferenceException if set to null (like 1.0/1.1)
215                         SaltValue = (byte[]) value.Clone ();
216 #endif
217                 }
218         }
219
220         public byte[] CryptDeriveKey (string algname, string alghashname, int keySize, byte[] rgbIV) 
221         {
222                 if (keySize > 128) {
223                         throw new CryptographicException (
224                                 Locale.GetText ("Key Size can't be greater than 128 bits"));
225                 }
226                 throw new NotSupportedException (
227                         Locale.GetText ("CspParameters not supported by Mono"));
228         }
229
230         // note: Key is returned - we can't zeroize it ourselve :-(
231 #if NET_2_0
232         [Obsolete ("see Rfc2898DeriveBytes for PKCS#5 v2 support")]
233 #endif
234         public override byte[] GetBytes (int cb) 
235         {
236 #if ! NET_2_0
237                 // 1.0/1.1 was a little late at throwing the argument exception ;-)
238                 if (password == null)
239                         throw new ArgumentNullException ("Password");
240 #endif
241                 if (cb < 1)
242                         throw new IndexOutOfRangeException ("cb");
243
244                 if (state == 0) {
245                         // it's now impossible to change the HashName, Salt
246                         // and IterationCount
247                         Reset ();
248                         state = 1;
249                 }
250
251                 byte[] result = new byte [cb];
252                 int cpos = 0;
253                 // the initial hash (in reset) + at least one iteration
254                 int iter = Math.Max (1, IterationsValue - 1);
255
256                 // start with the PKCS5 key
257                 if (output == null) {
258                         // calculate the PKCS5 key
259                         output = initial;
260
261                         // generate new key material
262                         for (int i = 0; i < iter - 1; i++)
263                                 output = hash.ComputeHash (output);
264                 }
265
266                 while (cpos < cb) {
267                         byte[] output2 = null;
268                         if (hashnumber == 0) {
269                                 // last iteration on output
270                                 output2 = hash.ComputeHash (output);
271                         }
272                         else if (hashnumber < 1000) {
273                                 string n = Convert.ToString (hashnumber);
274                                 output2 = new byte [output.Length + n.Length];
275                                 for (int j=0; j < n.Length; j++)
276                                         output2 [j] = (byte)(n [j]);
277                                 Buffer.BlockCopy (output, 0, output2, n.Length, output.Length);
278                                 // don't update output
279                                 output2 = hash.ComputeHash (output2);
280                         }
281                         else {
282                                 throw new CryptographicException (
283                                         Locale.GetText ("too long"));
284                         }
285
286                         int rem = output2.Length - position;
287                         int l = Math.Min (cb - cpos, rem);
288                         Buffer.BlockCopy (output2, position, result, cpos, l);
289                         cpos += l;
290                         position += l;
291                         while (position >= output2.Length) {
292                                 position -= output2.Length;
293                                 hashnumber++;
294                         }
295                 }
296                 return result;
297         }
298
299         public override void Reset () 
300         {
301 #if NET_2_0
302                 state = 0;
303 #else
304                 // note: Reset doesn't change state
305 #endif
306                 position = 0;
307                 hashnumber = 0;
308
309                 hash = HashAlgorithm.Create (HashNameValue);
310                 if (SaltValue != null) {
311                         hash.TransformBlock (password, 0, password.Length, password, 0);
312                         hash.TransformFinalBlock (SaltValue, 0, SaltValue.Length);
313                         initial = hash.Hash;
314                 }
315                 else
316                         initial = hash.ComputeHash (password);
317         }
318
319         
320