1 module crypto.rsa;
2 
3 import std.bigint;
4 import std.bitmanip;
5 import std.datetime;
6 import std.base64;
7 import std.typecons;
8 
9 import crypto.bigint;
10 import crypto.random;
11 public import crypto.padding;
12 
13 struct RSAKeyPair
14 {
15     string privateKey;
16     string publicKey;
17 
18     this(string privateKey, string publicKey)
19     {
20         this.privateKey = privateKey;
21         this.publicKey = publicKey;
22     }
23 }
24 
25 struct RSAKeyInfo
26 {
27     @property BigInt modulus()
28     {
29         return _modulus;
30     }
31 
32     @property ubyte[] modulus_bytes()
33     {
34         return _modulus_bytes;
35     }
36 
37     @property BigInt exponent()
38     {
39         return _exponent;
40     }
41 
42     @property ubyte[] exponent_bytes()
43     {
44         return _exponent_bytes;
45     }
46 
47     this(BigInt modulus, ubyte[] modulus_bytes, BigInt exponent, ubyte[] exponent_bytes)
48     {
49         _modulus = modulus;
50         _modulus_bytes = modulus_bytes;
51         _exponent = exponent;
52         _exponent_bytes = exponent_bytes;
53     }
54 
55 private:
56 
57     BigInt _modulus;
58     ubyte[] _modulus_bytes;
59     BigInt _exponent;
60     ubyte[] _exponent_bytes;
61 }
62 
63 class RSA
64 {
65 public:
66 
67     static RSAKeyPair generateKeyPair(uint bitLength = 2048)
68     {
69         assert((bitLength >= 128) && (bitLength % 8 == 0),
70             "Bitlength is required to be a multiple of 8 and not less than 128. It’s recommended that it be no less than 2048.");
71 
72         BigInt x, y;
73 
74         BigInt ex_gcd(BigInt a, BigInt b)
75         {
76             if (b == 0)
77             {
78                 x = BigInt("1");
79                 y = BigInt("0");
80                 return a;
81             }
82 
83             BigInt ans = ex_gcd(b, a % b);
84             BigInt temp = x;
85             x = y;
86             y = temp - (a / b * y);
87 
88             return ans;
89         }
90 
91         BigInt cal(BigInt a, BigInt k)
92         {
93             BigInt gcd = ex_gcd(a, k);
94 
95             if (gcd % 1 != 0)
96             {
97                 return BigInt("-1");
98             }
99 
100             x = x * (1 / gcd);
101 
102             if (k < 0)
103             {
104                 k *= -1;
105             }
106 
107             BigInt ans = x % k;
108 
109             if (ans < 0)
110             {
111                 ans += k;
112             }
113 
114             return ans;
115         }
116 
117         size_t confidence;
118         if (bitLength <= 128)       confidence = 50;
119         else if (bitLength <= 256)  confidence = 27;
120         else if (bitLength <= 512)  confidence = 15;
121         else if (bitLength <= 768)  confidence = 8;
122         else if (bitLength <= 1024) confidence = 4;
123         else                        confidence = 2;
124 
125         BigInt p, q, n, t, e, d;
126 
127         do
128         {
129             p = BigIntHelper.randomGenerate(bitLength / 2, 1, 1);
130         }
131         while (!BigIntHelper.isProbablePrime(p, confidence));
132         do
133         {
134             q = BigIntHelper.randomGenerate(bitLength / 2, 1, 1);
135         }
136         while (!BigIntHelper.isProbablePrime(q, confidence));
137 
138         n = p * q;
139         t = (p - 1) * (q - 1);
140         e = 65537;
141         d = cal(e, t);
142 
143         return RSAKeyPair(encodeKey(n, d), encodeKey(n, e));
144     }
145 
146     static string encodeKey(T : iPKCS = SimpleFormat)(BigInt modulus, BigInt exponent)
147     {
148         return T.encodeKey(modulus, exponent);
149     }
150 
151     static RSAKeyInfo decodeKey(T : iPKCS = SimpleFormat)(string key)
152     {
153         return T.decodeKey(key).get;
154     }
155 
156     static ubyte[] encrypt(T : iPKCS = SimpleFormat)(string key, ubyte[] data, bool mixinXteaMode = false)
157     {
158         return crypt!("encrypt", T)(key, data, mixinXteaMode);
159     }
160 
161     static ubyte[] encrypt(RSAKeyInfo key, ubyte[] data, bool mixinXteaMode = false)
162     {
163         return crypt!"encrypt"(key, data, mixinXteaMode);
164     }
165 
166     static ubyte[] decrypt(T : iPKCS = SimpleFormat)(string key, ubyte[] data, bool mixinXteaMode = false)
167     {
168         return crypt!("decrypt", T)(key, data, mixinXteaMode);
169     }
170 
171     static ubyte[] decrypt(RSAKeyInfo key, ubyte[] data, bool mixinXteaMode = false)
172     {
173         return crypt!"decrypt"(key, data, mixinXteaMode);
174     }
175 
176 private:
177 
178     static ubyte[] crypt(string T1, T2 : iPKCS = SimpleFormat)(string key, ubyte[] data, bool mixinXteaMode)
179     if (T1 == "encrypt" || T1 == "decrypt")
180     {
181         RSAKeyInfo ki = decodeKey!T2(key);
182         return crypt!(T1)(ki, data, mixinXteaMode);
183     }
184 
185     static ubyte[] crypt(string T)(RSAKeyInfo key, ubyte[] data, bool mixinXteaMode)
186     if (T == "encrypt" || T == "decrypt")
187     {
188         if (mixinXteaMode)
189         {
190             return crypt_mixinXteaMode!T(key, data);
191         }
192         
193         size_t keySize = key.modulus_bytes.length;
194 
195         BigInt getNextBlock(out size_t blockSize)
196         {
197             if (data.length == 0)
198             {
199                 blockSize = 0;
200                 return BigInt("0");
201             }
202 
203             static if (T == "decrypt")
204             {
205                 ubyte[] block = data[0 .. ($ >= keySize) ? keySize : $];
206                 blockSize = block.length;
207                 return BigIntHelper.fromBytes(block);
208             }
209             else
210             {
211                 // Prevent preamble 0, and make the encrypto results random
212                 ubyte preamble = rnd.next!ubyte(0x01, 0xFF);
213                 blockSize = (keySize <= data.length) ? keySize : data.length;
214 
215                 while (true)
216                 {
217                     ubyte[] block = [preamble] ~ data[0 .. blockSize];
218                     BigInt t = BigIntHelper.fromBytes(block);
219                     if (t >= key.modulus)
220                     {
221                         blockSize--;
222                         assert(blockSize > 0, "Key bits is too small.");
223                         continue;
224                     }
225                     return t;
226                 }
227             }
228         }
229 
230         ubyte[] ret;
231 
232         while (data.length > 0)
233         {
234             size_t blockSize;
235             BigInt block = getNextBlock(blockSize);
236             if (blockSize == 0)
237             {
238                 break;
239             }
240 
241             block = BigIntHelper.powmod(block, key.exponent, key.modulus);
242             ubyte[] block_buf = BigIntHelper.toUBytes(block);
243             static if (T == "encrypt")
244             {
245                 for (size_t i; i < keySize - block_buf.length; i++)
246                 {
247                     ret ~= cast(ubyte)0;
248                 }
249             }
250             else
251             {
252                 block_buf = block_buf[1 .. $];
253             }
254 
255             ret ~= block_buf;
256             data = data[blockSize .. $];
257         }
258 
259         return ret;
260     }
261     
262     static ubyte[] crypt_mixinXteaMode(string T)(RSAKeyInfo key, ubyte[] data)
263     if (T == "encrypt" || T == "decrypt")
264     {
265         import crypto.tea;
266 
267         int[4] xteaKey;
268         int rounds = 64;
269         size_t keySize = key.modulus_bytes.length;
270 
271         void generateXteaKey(in ubyte[] buf)
272         {
273             ubyte[] data = new ubyte[int.sizeof * 4];
274             for (int i = 0; i < int.sizeof * 4; i++)
275             {
276                 data[i] = buf[i % buf.length];
277             }
278 
279             for (int i = 0; i < 4; i++)
280             {
281                 xteaKey[i] = data.peek!int(i * int.sizeof);
282             } 
283         }
284 
285         BigInt getNextBlock(out size_t blockSize)
286         {
287             if (data.length == 0)
288             {
289                 blockSize = 0;
290                 return BigInt("0");
291             }
292 
293             static if (T == "decrypt")
294             {
295                 ubyte[] block = data[0 .. ($ >= keySize) ? keySize : $];
296                 blockSize = block.length;
297                 return BigIntHelper.fromBytes(block);
298             }
299             else
300             {
301                 // Prevent preamble 0, and make the encrypto results random
302                 ubyte preamble = rnd.next!ubyte(0x01, 0xFF);
303                 blockSize = (keySize <= data.length) ? keySize : data.length;
304 
305                 while (true)
306                 {
307                     ubyte[] block = [preamble] ~ data[0 .. blockSize];
308                     BigInt t = BigIntHelper.fromBytes(block);
309                     if (t >= key.modulus)
310                     {
311                         blockSize--;
312                         assert(blockSize > 0, "Key bits is too small.");
313                         continue;
314                     }
315 
316                     generateXteaKey(block);
317                     return t;
318                 }
319             }
320         }
321 
322         ubyte[] ret;
323 
324         size_t blockSize;
325         BigInt block = getNextBlock(blockSize);
326         if (blockSize == 0)
327         {
328             return ret;
329         }
330 
331         block = BigIntHelper.powmod(block, key.exponent, key.modulus);
332         ubyte[] block_buf = BigIntHelper.toUBytes(block);
333         static if (T == "encrypt")
334         {
335             for (size_t i; i < keySize - block_buf.length; i++)
336             {
337                 ret ~= cast(ubyte)0;
338             }
339         }
340         else
341         {
342             generateXteaKey(block_buf);
343             block_buf = block_buf[1 .. $];
344         }
345 
346         ret ~= block_buf;
347 
348         if (blockSize >= data.length)
349         {
350             return ret;
351         }
352 
353         data = data[blockSize .. $];
354 
355         static if (T == "encrypt")
356         {
357             ret ~= Xtea.encrypt(data, xteaKey, rounds, PaddingMode.Customized);
358         }
359         else
360         {
361             ret ~= Xtea.decrypt(data, xteaKey, rounds, PaddingMode.Customized);
362         }
363 
364         return ret;
365     }
366 }
367 
368 interface iPKCS
369 {
370     static string encodeKey(BigInt modulus, BigInt exponent);
371     static Nullable!RSAKeyInfo decodeKey(string key);
372 }
373 
374 class SimpleFormat : iPKCS
375 {
376     static string encodeKey(BigInt modulus, BigInt exponent)
377     {
378         ubyte[] m_bytes = BigIntHelper.toUBytes(modulus);
379         ubyte[] e_bytes = BigIntHelper.toUBytes(exponent);
380 
381         ubyte[] buffer = new ubyte[4];
382 
383         buffer.write!int(cast(int) m_bytes.length, 0);
384         buffer ~= m_bytes;
385         buffer ~= e_bytes;
386 
387         return Base64.encode(buffer);
388     }
389 
390     static Nullable!RSAKeyInfo decodeKey(string key)
391     {
392         ubyte[] buffer = Base64.decode(key);
393         int m_len = buffer.peek!int(0);
394         ubyte[] modulus_bytes = buffer[4 .. 4 + m_len];
395         ubyte[] exponent_bytes = buffer[4 + m_len .. $];
396 
397         return Nullable!RSAKeyInfo(RSAKeyInfo(BigIntHelper.fromBytes(modulus_bytes),
398                 modulus_bytes, BigIntHelper.fromBytes(exponent_bytes), exponent_bytes));
399     }
400 }
401 
402 class PKCS1 : iPKCS
403 {
404     static string encodeKey(BigInt modulus, BigInt exponent)
405     {
406         return string.init;
407     }
408 
409     static Nullable!RSAKeyInfo decodeKey(string key)
410     {
411         return Nullable!RSAKeyInfo();
412     }
413 }
414 
415 class PKCS8 : iPKCS
416 {
417     static string encodeKey(BigInt modulus, BigInt exponent)
418     {
419         return string.init;
420     }
421 
422     static Nullable!RSAKeyInfo decodeKey(string key)
423     {
424         return Nullable!RSAKeyInfo();
425     }
426 }
427 
428 unittest
429 {
430     import crypto.rsa;
431 
432     RSAKeyPair keyPair = RSA.generateKeyPair(1024);
433 
434     string data = `
435 And the workload proves (POW) reusable workload proof (RPOW) 2. hash function
436 The hash function (Hash Function), also known as a hash function, gives an input x, which calculates the corresponding output H (x). The main features of a hash function are:
437 The input x can be a string of any length
438 The output, that is, the length of H (x) is fixed
439 
440 The procedure for calculating H (x) is efficient (for string X of length n), the time complexity of H (x) should be O (n)
441 For bitcoin, the hash function used by such cryptographic systems, it needs to have the following properties:
442     `;
443 
444     ubyte[] sb = cast(ubyte[]) data;
445     ubyte[] db = RSA.encrypt(keyPair.privateKey, sb);
446     sb = RSA.decrypt(keyPair.publicKey, db);
447     assert(cast(string) sb == data);
448 }
449 
450 // for mixinXteaMode
451 unittest
452 {
453     import crypto.rsa;
454 
455     RSAKeyInfo pri_key = RSA.decodeKey("AAAAgIcksjBHK7FYePatdsIWiHv7Z+HixIcD0dMyEtXLAGf9fDujn05Seyz1mTIE62UKMojVK+O4oxVT9Ljf8w5F81dTnanoKH0+I/lQN5CR95nRgUEwhZFSbmYM2YeACsfPyX1V5iP0KU8LN9D7/7q64lSZ6XbHGpa7DRv7yvDHaMyJBkGipi2FTk6EOxdIui+E3giDhKeU5ZM9sYNN7+vX9vh7Od+XTm7vGOO91dz4cNMKB9+mioJPunsKh0yG2hBO9Zg+IZNWir/jRfG/w38Av4pQOkRsz9U/ZsTA37XKqzglnwaKGPSw51RJuWuv9RXDRh12Po6oLnr5TkK0OBeJPgE=");
456     RSAKeyInfo pub_key = RSA.decodeKey("AAAAgIcksjBHK7FYePatdsIWiHv7Z+HixIcD0dMyEtXLAGf9fDujn05Seyz1mTIE62UKMojVK+O4oxVT9Ljf8w5F81dTnanoKH0+I/lQN5CR95nRgUEwhZFSbmYM2YeACsfPyX1V5iP0KU8LN9D7/7q64lSZ6XbHGpa7DRv7yvDHaMyJAQAB");
457 
458     string data = `
459 And the workload proves (POW) reusable workload proof (RPOW) 2. hash function
460 The hash function (Hash Function), also known as a hash function, gives an input x, which calculates the corresponding output H (x). The main features of a hash function are:
461 The input x can be a string of any length
462 The output, that is, the length of H (x) is fixed
463 
464 The procedure for calculating H (x) is efficient (for string X of length n), the time complexity of H (x) should be O (n)
465 For bitcoin, the hash function used by such cryptographic systems, it needs to have the following properties:
466     `;
467 
468     ubyte[] sb = cast(ubyte[]) data;
469     ubyte[] db = RSA.encrypt(pri_key, sb, true);
470     sb = RSA.decrypt(pub_key, db, true);
471     assert(cast(string) sb == data);
472 }