2004-04-08 Bernie Solomon <bernard@ugsolutions.com>
[mono.git] / mcs / class / corlib / Mono.Security.Cryptography / PKCS1.cs
1 //
2 // PKCS1.cs - Implements PKCS#1 primitives.
3 //
4 // Author:
5 //      Sebastien Pouliot (spouliot@motus.com)
6 //
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
8 //
9
10 using System;
11 using System.Security.Cryptography;
12
13 namespace Mono.Security.Cryptography { 
14
15         // References:
16         // a.   PKCS#1: RSA Cryptography Standard 
17         //      http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
18         
19 #if INSIDE_CORLIB
20         internal
21 #else
22         public
23 #endif
24         class PKCS1 {
25         
26                 private static bool Compare (byte[] array1, byte[] array2) 
27                 {
28                         bool result = (array1.Length == array2.Length);
29                         if (result) {
30                                 for (int i=0; i < array1.Length; i++)
31                                         if (array1[i] != array2[i])
32                                                 return false;
33                         }
34                         return result;
35                 }
36         
37                 private static byte[] xor (byte[] array1, byte[] array2) 
38                 {
39                         byte[] result = new byte [array1.Length];
40                         for (int i=0; i < result.Length; i++)
41                                 result[i] = (byte) (array1[i] ^ array2[i]);
42                         return result;
43                 }
44         
45                 private static byte[] emptySHA1   = { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 };
46                 private static byte[] emptySHA256 = { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 };
47                 private static byte[] emptySHA384 = { 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b };
48                 private static byte[] emptySHA512 = { 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e };
49         
50                 private static byte[] GetEmptyHash (HashAlgorithm hash) 
51                 {
52                         if (hash is SHA1)
53                                 return emptySHA1;
54                         else if (hash is SHA256)
55                                 return emptySHA256;
56                         else if (hash is SHA384)
57                                 return emptySHA384;
58                         else if (hash is SHA512)
59                                 return emptySHA512;
60                         else
61                                 return hash.ComputeHash ((byte[])null);
62                 }
63         
64                 // PKCS #1 v.2.1, Section 4.1
65                 // I2OSP converts a non-negative integer to an octet string of a specified length.
66                 public static byte[] I2OSP (int x, int size) 
67                 {
68                         byte[] array = BitConverterLE.GetBytes (x);
69                         Array.Reverse (array, 0, array.Length);
70                         return I2OSP (array, size);
71                 }
72         
73                 public static byte[] I2OSP (byte[] x, int size) 
74                 {
75                         byte[] result = new byte [size];
76                         Array.Copy (x, 0, result, (result.Length - x.Length), x.Length);
77                         return result;
78                 }
79         
80                 // PKCS #1 v.2.1, Section 4.2
81                 // OS2IP converts an octet string to a nonnegative integer.
82                 public static byte[] OS2IP (byte[] x) 
83                 {
84                         int i = 0;
85                         while ((x [i++] == 0x00) && (i < x.Length));
86                         i--;
87                         if (i > 0) {
88                                 byte[] result = new byte [x.Length - i];
89                                 Array.Copy (x, i, result, 0, result.Length);
90                                 return result;
91                         }
92                         else
93                                 return x;
94                 }
95         
96                 // PKCS #1 v.2.1, Section 5.1.1
97                 public static byte[] RSAEP (RSA rsa, byte[] m) 
98                 {
99                         // c = m^e mod n
100                         return rsa.EncryptValue (m);
101                 }
102         
103                 // PKCS #1 v.2.1, Section 5.1.2
104                 public static byte[] RSADP (RSA rsa, byte[] c) 
105                 {
106                         // m = c^d mod n
107                         // Decrypt value may apply CRT optimizations
108                         return rsa.DecryptValue (c);
109                 }
110         
111                 // PKCS #1 v.2.1, Section 5.2.1
112                 public static byte[] RSASP1 (RSA rsa, byte[] m) 
113                 {
114                         // first form: s = m^d mod n
115                         // Decrypt value may apply CRT optimizations
116                         return rsa.DecryptValue (m);
117                 }
118         
119                 // PKCS #1 v.2.1, Section 5.2.2
120                 public static byte[] RSAVP1 (RSA rsa, byte[] s) 
121                 {
122                         // m = s^e mod n
123                         return rsa.EncryptValue (s);
124                 }
125         
126                 // PKCS #1 v.2.1, Section 7.1.1
127                 // RSAES-OAEP-ENCRYPT ((n, e), M, L)
128                 public static byte[] Encrypt_OAEP (RSA rsa, HashAlgorithm hash, RandomNumberGenerator rng, byte[] M) 
129                 {
130                         int size = rsa.KeySize / 8;
131                         int hLen = hash.HashSize / 8;
132                         if (M.Length > size - 2 * hLen - 2)
133                                 throw new CryptographicException ("message too long");
134                         // empty label L SHA1 hash
135                         byte[] lHash = GetEmptyHash (hash);
136                         int PSLength = (size - M.Length - 2 * hLen - 2);
137                         // DB = lHash || PS || 0x01 || M
138                         byte[] DB = new byte [lHash.Length + PSLength + 1 + M.Length];
139                         Array.Copy (lHash, 0, DB, 0, lHash.Length);
140                         DB [(lHash.Length + PSLength)] = 0x01;
141                         Array.Copy (M, 0, DB, (DB.Length - M.Length), M.Length);
142         
143                         byte[] seed = new byte [hLen];
144                         rng.GetBytes (seed);
145         
146                         byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
147                         byte[] maskedDB = xor (DB, dbMask);
148                         byte[] seedMask = MGF1 (hash, maskedDB, hLen);
149                         byte[] maskedSeed = xor (seed, seedMask);
150                         // EM = 0x00 || maskedSeed || maskedDB
151                         byte[] EM = new byte [maskedSeed.Length + maskedDB.Length + 1];
152                         Array.Copy (maskedSeed, 0, EM, 1, maskedSeed.Length);
153                         Array.Copy (maskedDB, 0, EM, maskedSeed.Length + 1, maskedDB.Length);
154         
155                         byte[] m = OS2IP (EM);
156                         byte[] c = RSAEP (rsa, m);
157                         return I2OSP (c, size);
158                 }
159         
160                 // PKCS #1 v.2.1, Section 7.1.2
161                 // RSAES-OAEP-DECRYPT (K, C, L)
162                 public static byte[] Decrypt_OAEP (RSA rsa, HashAlgorithm hash, byte[] C) 
163                 {
164                         int size = rsa.KeySize / 8;
165                         int hLen = hash.HashSize / 8;
166                         if ((size < (2 * hLen + 2)) || (C.Length != size))
167                                 throw new CryptographicException ("decryption error");
168         
169                         byte[] c = OS2IP (C);
170                         byte[] m = RSADP (rsa, c);
171                         byte[] EM = I2OSP (m, size);
172         
173                         // split EM = Y || maskedSeed || maskedDB
174                         byte[] maskedSeed = new byte [hLen];
175                         Array.Copy (EM, 1, maskedSeed, 0, maskedSeed.Length);
176                         byte[] maskedDB = new byte [size - hLen - 1];
177                         Array.Copy (EM, (EM.Length - maskedDB.Length), maskedDB, 0, maskedDB.Length);
178         
179                         byte[] seedMask = MGF1 (hash, maskedDB, hLen);
180                         byte[] seed = xor (maskedSeed, seedMask);
181                         byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
182                         byte[] DB = xor (maskedDB, dbMask);
183         
184                         byte[] lHash = GetEmptyHash (hash);
185                         // split DB = lHash' || PS || 0x01 || M
186                         byte[] dbHash = new byte [lHash.Length];
187                         Array.Copy (DB, 0, dbHash, 0, dbHash.Length);
188                         bool h = Compare (lHash, dbHash);
189         
190                         // find separator 0x01
191                         int nPos = lHash.Length;
192                         while (DB[nPos] == 0)
193                                 nPos++;
194         
195                         int Msize = DB.Length - nPos - 1;
196                         byte[] M = new byte [Msize];
197                         Array.Copy (DB, (nPos + 1), M, 0, Msize);
198         
199                         // we could have returned EM[0] sooner but would be helping a timing attack
200                         if ((EM[0] != 0) || (!h) || (DB[nPos] != 0x01))
201                                 return null;
202                         return M;
203                 }
204         
205                 // PKCS #1 v.2.1, Section 7.2.1
206                 // RSAES-PKCS1-V1_5-ENCRYPT ((n, e), M)
207                 public static byte[] Encrypt_v15 (RSA rsa, RandomNumberGenerator rng, byte[] M) 
208                 {
209                         int size = rsa.KeySize / 8;
210                         if (M.Length > size - 11)
211                                 throw new CryptographicException ("message too long");
212                         int PSLength = System.Math.Max (8, (size - M.Length - 3));
213                         byte[] PS = new byte [PSLength];
214                         rng.GetNonZeroBytes (PS);
215                         byte[] EM = new byte [size];
216                         EM [1] = 0x02;
217                         Array.Copy (PS, 0, EM, 2, PSLength);
218                         Array.Copy (M, 0, EM, (size - M.Length), M.Length);
219         
220                         byte[] m = OS2IP (EM);
221                         byte[] c = RSAEP (rsa, m);
222                         byte[] C = I2OSP (c, size);
223                         return C;
224                 }
225         
226                 // PKCS #1 v.2.1, Section 7.2.2
227                 // RSAES-PKCS1-V1_5-DECRYPT (K, C)
228                 public static byte[] Decrypt_v15 (RSA rsa, byte[] C) 
229                 {
230                         int size = rsa.KeySize / 8;
231                         if ((size < 11) || (C.Length != size))
232                                 throw new CryptographicException ("decryption error");
233                         byte[] c = OS2IP (C);
234                         byte[] m = RSADP (rsa, c);
235                         byte[] EM = I2OSP (m, size);
236         
237                         if ((EM [0] != 0x00) || (EM [1] != 0x02))
238                                 return null;
239         
240                         int mPos = 10;
241                         // PS is a minimum of 8 bytes + 2 bytes for header
242                         while ((EM [mPos] != 0x00) && (mPos < EM.Length))
243                                 mPos++;
244                         if (EM [mPos] != 0x00)
245                                 return null;
246                         mPos++;
247                         byte[] M = new byte [EM.Length - mPos];
248                         Array.Copy (EM, mPos, M, 0, M.Length);
249                         return M;
250                 }
251         
252                 // PKCS #1 v.2.1, Section 8.2.1
253                 // RSASSA-PKCS1-V1_5-SIGN (K, M)
254                 public static byte[] Sign_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue) 
255                 {
256                         int size = (rsa.KeySize >> 3); // div 8
257                         byte[] EM = Encode_v15 (hash, hashValue, size);
258                         byte[] m = OS2IP (EM);
259                         byte[] s = RSASP1 (rsa, m);
260                         byte[] S = I2OSP (s, size);
261                         return S;
262                 }
263         
264                 // PKCS #1 v.2.1, Section 8.2.2
265                 // RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
266                 public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue, byte[] signature) 
267                 {
268                         int size = (rsa.KeySize >> 3); // div 8
269                         byte[] s = OS2IP (signature);
270                         byte[] m = RSAVP1 (rsa, s);
271                         byte[] EM2 = I2OSP (m, size);
272                         byte[] EM = Encode_v15 (hash, hashValue, size);
273                         bool result = Compare (EM, EM2);
274                         if (!result) {
275                                 // NOTE: some signatures don't include the hash OID (pretty lame but real)
276                                 // and compatible with MS implementation
277                                 if ((EM2 [0] != 0x00) || (EM2 [1] != 0x01))
278                                         return false;
279                                 // TODO: add more validation
280                                 byte[] decryptedHash = new byte [hashValue.Length];
281                                 Array.Copy (EM2, EM2.Length - hashValue.Length, decryptedHash, 0, decryptedHash.Length);
282                                 result = Compare (decryptedHash, hashValue);
283                         }
284                         return result;
285                 }
286         
287                 // PKCS #1 v.2.1, Section 9.2
288                 // EMSA-PKCS1-v1_5-Encode
289                 public static byte[] Encode_v15 (HashAlgorithm hash, byte[] hashValue, int emLength) 
290                 {
291                         if (hashValue.Length != (hash.HashSize >> 3))
292                                 throw new CryptographicException ("bad hash length for " + hash.ToString ());
293
294                         // DigestInfo ::= SEQUENCE {
295                         //      digestAlgorithm AlgorithmIdentifier,
296                         //      digest OCTET STRING
297                         // }
298                 
299                         byte[] t = null;
300
301                         string oid = CryptoConfig.MapNameToOID (hash.ToString ());
302                         if (oid != null)
303                         {
304                                 ASN1 digestAlgorithm = new ASN1 (0x30);
305                                 digestAlgorithm.Add (new ASN1 (CryptoConfig.EncodeOID (oid)));
306                                 digestAlgorithm.Add (new ASN1 (0x05));          // NULL
307                                 ASN1 digest = new ASN1 (0x04, hashValue);
308                                 ASN1 digestInfo = new ASN1 (0x30);
309                                 digestInfo.Add (digestAlgorithm);
310                                 digestInfo.Add (digest);
311
312                                 t = digestInfo.GetBytes ();
313                         }
314                         else
315                         {
316                                 // There are no valid OID, in this case t = hashValue
317                                 // This is the case of the MD5SHA hash algorithm
318                                 t = hashValue;
319                         }
320
321                         Array.Copy (hashValue, 0, t, t.Length - hashValue.Length, hashValue.Length);
322         
323                         int PSLength = System.Math.Max (8, emLength - t.Length - 3);
324                         // PS = PSLength of 0xff
325         
326                         // EM = 0x00 | 0x01 | PS | 0x00 | T
327                         byte[] EM = new byte [PSLength + t.Length + 3];
328                         EM [1] = 0x01;
329                         for (int i=2; i < PSLength + 2; i++)
330                                 EM[i] = 0xff;
331                         Array.Copy (t, 0, EM, PSLength + 3, t.Length);
332         
333                         return EM;
334                 }
335         
336                 // PKCS #1 v.2.1, Section B.2.1
337                 public static byte[] MGF1 (HashAlgorithm hash, byte[] mgfSeed, int maskLen) 
338                 {
339                         // 1. If maskLen > 2^32 hLen, output "mask too long" and stop.
340                         // easy - this is impossible by using a int (31bits) as parameter ;-)
341                         // BUT with a signed int we do have to check for negative values!
342                         if (maskLen < 0)
343                                 throw new OverflowException();
344         
345                         int mgfSeedLength = mgfSeed.Length;
346                         int hLen = (hash.HashSize >> 3); // from bits to bytes
347                         int iterations = (maskLen / hLen);
348                         if (maskLen % hLen != 0)
349                                 iterations++;
350                         // 2. Let T be the empty octet string.
351                         byte[] T = new byte [iterations * hLen];
352         
353                         byte[] toBeHashed = new byte [mgfSeedLength + 4];
354                         int pos = 0;
355                         // 3. For counter from 0 to \ceil (maskLen / hLen) - 1, do the following:
356                         for (int counter = 0; counter < iterations; counter++) {
357                                 // a.   Convert counter to an octet string C of length 4 octets
358                                 byte[] C = I2OSP (counter, 4); 
359         
360                                 // b.   Concatenate the hash of the seed mgfSeed and C to the octet string T:
361                                 //      T = T || Hash (mgfSeed || C)
362                                 Array.Copy (mgfSeed, 0, toBeHashed, 0, mgfSeedLength);
363                                 Array.Copy (C, 0, toBeHashed, mgfSeedLength, 4);
364                                 byte[] output = hash.ComputeHash (toBeHashed);
365                                 Array.Copy (output, 0, T, pos, hLen);
366                                 pos += mgfSeedLength;
367                         }
368                         
369                         // 4. Output the leading maskLen octets of T as the octet string mask.
370                         byte[] mask = new byte [maskLen];
371                         Array.Copy (T, 0, mask, 0, maskLen);
372                         return mask;
373                 }
374         }
375 }