2 // PKCS1.cs - Implements PKCS#1 primitives.
5 // Sebastien Pouliot (spouliot@motus.com)
7 // (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
11 using System.Security.Cryptography;
13 namespace Mono.Security.Cryptography {
16 // a. PKCS#1: RSA Cryptography Standard
17 // http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
26 private static bool Compare (byte[] array1, byte[] array2)
28 bool result = (array1.Length == array2.Length);
30 for (int i=0; i < array1.Length; i++)
31 if (array1[i] != array2[i])
37 private static byte[] xor (byte[] array1, byte[] array2)
39 byte[] result = new byte [array1.Length];
40 for (int i=0; i < result.Length; i++)
41 result[i] = (byte) (array1[i] ^ array2[i]);
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 };
50 private static byte[] GetEmptyHash (HashAlgorithm hash)
54 else if (hash is SHA256)
56 else if (hash is SHA384)
58 else if (hash is SHA512)
61 return hash.ComputeHash ((byte[])null);
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)
68 byte[] array = BitConverterLE.GetBytes (x);
69 Array.Reverse (array, 0, array.Length);
70 return I2OSP (array, size);
73 public static byte[] I2OSP (byte[] x, int size)
75 byte[] result = new byte [size];
76 Array.Copy (x, 0, result, (result.Length - x.Length), x.Length);
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)
85 while ((x [i++] == 0x00) && (i < x.Length));
88 byte[] result = new byte [x.Length - i];
89 Array.Copy (x, i, result, 0, result.Length);
96 // PKCS #1 v.2.1, Section 5.1.1
97 public static byte[] RSAEP (RSA rsa, byte[] m)
100 return rsa.EncryptValue (m);
103 // PKCS #1 v.2.1, Section 5.1.2
104 public static byte[] RSADP (RSA rsa, byte[] c)
107 // Decrypt value may apply CRT optimizations
108 return rsa.DecryptValue (c);
111 // PKCS #1 v.2.1, Section 5.2.1
112 public static byte[] RSASP1 (RSA rsa, byte[] m)
114 // first form: s = m^d mod n
115 // Decrypt value may apply CRT optimizations
116 return rsa.DecryptValue (m);
119 // PKCS #1 v.2.1, Section 5.2.2
120 public static byte[] RSAVP1 (RSA rsa, byte[] s)
123 return rsa.EncryptValue (s);
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)
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);
143 byte[] seed = new byte [hLen];
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);
155 byte[] m = OS2IP (EM);
156 byte[] c = RSAEP (rsa, m);
157 return I2OSP (c, size);
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)
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");
169 byte[] c = OS2IP (C);
170 byte[] m = RSADP (rsa, c);
171 byte[] EM = I2OSP (m, size);
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);
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);
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);
190 // find separator 0x01
191 int nPos = lHash.Length;
192 while (DB[nPos] == 0)
195 int Msize = DB.Length - nPos - 1;
196 byte[] M = new byte [Msize];
197 Array.Copy (DB, (nPos + 1), M, 0, Msize);
199 // we could have returned EM[0] sooner but would be helping a timing attack
200 if ((EM[0] != 0) || (!h) || (DB[nPos] != 0x01))
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)
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];
217 Array.Copy (PS, 0, EM, 2, PSLength);
218 Array.Copy (M, 0, EM, (size - M.Length), M.Length);
220 byte[] m = OS2IP (EM);
221 byte[] c = RSAEP (rsa, m);
222 byte[] C = I2OSP (c, size);
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)
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);
237 if ((EM [0] != 0x00) || (EM [1] != 0x02))
241 // PS is a minimum of 8 bytes + 2 bytes for header
242 while ((EM [mPos] != 0x00) && (mPos < EM.Length))
244 if (EM [mPos] != 0x00)
247 byte[] M = new byte [EM.Length - mPos];
248 Array.Copy (EM, mPos, M, 0, M.Length);
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)
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);
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)
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);
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))
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);
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)
291 if (hashValue.Length != (hash.HashSize >> 3))
292 throw new CryptographicException ("bad hash length for " + hash.ToString ());
294 // DigestInfo ::= SEQUENCE {
295 // digestAlgorithm AlgorithmIdentifier,
296 // digest OCTET STRING
301 string oid = CryptoConfig.MapNameToOID (hash.ToString ());
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);
312 t = digestInfo.GetBytes ();
316 // There are no valid OID, in this case t = hashValue
317 // This is the case of the MD5SHA hash algorithm
321 Array.Copy (hashValue, 0, t, t.Length - hashValue.Length, hashValue.Length);
323 int PSLength = System.Math.Max (8, emLength - t.Length - 3);
324 // PS = PSLength of 0xff
326 // EM = 0x00 | 0x01 | PS | 0x00 | T
327 byte[] EM = new byte [PSLength + t.Length + 3];
329 for (int i=2; i < PSLength + 2; i++)
331 Array.Copy (t, 0, EM, PSLength + 3, t.Length);
336 // PKCS #1 v.2.1, Section B.2.1
337 public static byte[] MGF1 (HashAlgorithm hash, byte[] mgfSeed, int maskLen)
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!
343 throw new OverflowException();
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)
350 // 2. Let T be the empty octet string.
351 byte[] T = new byte [iterations * hLen];
353 byte[] toBeHashed = new byte [mgfSeedLength + 4];
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);
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;
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);