-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathelgamal.js
645 lines (573 loc) · 22.7 KB
/
elgamal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
/**
* ElGamal Module
* @module basic_simple_elgamal
*/
const bigInteger = require('big-integer');
const bigIntManager = require('./bigIntManager');
const debug = require('debug');
const https = require('https');
/**
* To access offline groups in offline mode:
*/
const fs = require('fs/promises');
/*
Below groups are just selected in offline mode and if you are in a browser:
*/
const defaultGroup2048Bit = require('./offlineGroups/2048/0');
const defaultGroup3072Bit = require('./offlineGroups/3072/0');
const defaultGroup4096Bit = require('./offlineGroups/4096/0');
const defaultGroup8192Bit = require('./offlineGroups/8192/0')
const log = debug('app::elgamal');
const securityLevels = ['HIGH', 'LOW', 'MEDIUM']
/**
* @typedef {'HIGH'|'LOW'|'MEDIUM'} securityLevel
*/
/**
* @typedef {Object} cipherText (refer to ElGamal Schema for more information).
* @property {bigInteger.BigInteger} c1 - This is half the shared secret: c1 = g^r
* @property {bigInteger.BigInteger} c2 - This is encrypted message: c2 = y^r . m
*/
/**
* @typedef {8192|4096|3072|2048} allowedLengthes
*/
/**
* @typedef {Object} Engine the object containing essential information to build the ElGamal
* cryptoengine again
* @property {bigInteger.BigInteger} p - The modulus of underlying group and determine the whole Cyclic group
* @property {bigInteger.BigInteger} g - The generator of underlying group.
* @property {bigInteger.BigInteger} y - The public key which is your public key and others can use it to
* encrypt messages for you.
* @property {bigInteger.BigInteger} [x] - The private key(decryption key) which is strongly recommended to don't export it
* @property {bigInteger.BigInteger} [r] - The secret key which is used in last encryption to build
* the cipherText.c1
* @property {securityLevel} [security] - The engine security level.
*/
class ElGamal{
/**
* Initialize the ElGamal Engine by giving private key, public key, modulus, group order, group generator.
* @param {string|bigInteger.BigInteger} [p] modulus of multiplicative group
* @param {string|bigInteger.BigInteger} [g] generator of the group
* @param {string|bigInteger.BigInteger} [y] public key(encryption key),
* NOTE: this is not your public key but public key of receiver.
* @param {string|bigInteger.BigInteger} [x] private key(decryption key)
* @throws Will throw an error if any of passed parameters is an invalid big integer!
*/
constructor(p, g, y, x){
/**
* @property {securityLevel} [securityLevel='HIGH'] - determines the security level of the engine.
* if it's set to 'HIGH' then you should use safe prime numbers as underlying group's modulus,
* else if it's set to 'MEDIUM' then you should use prime numbers as modulus,
* else it's set to 'LOW' then there is no condition but on other hands there is no guarantee
* that the engine operates correctly.
* @type {securityLevel}
*/
this.securityLevel = 'HIGH';
/**
* @property {boolean} [isSecure=false] - this property indicates whether engine is at expected
* secure level or not. If it's false then you will be unable to use the engines functionality.
* to Change this value to True, please call setSecurityLevel() method.
*/
this.isSecure = false;
/**
* @property {bigInteger.BigInteger} g - The generator of underlying multiplicative group.
*/
try{
if(typeof g === 'string')
this.g = bigInteger(g);
else if(g instanceof bigInteger)
this.g = g;
else
this.g = undefined;
}catch(err){
log('Error when initializing generator:', err);
throw new Error('Error: generator is not a valid big integer!');
}
/**
* @property {bigInteger.BigInteger} p - The modulus of underlying multiplicative group.
*/
try{
if(typeof p === 'string'){
this.p = bigInteger(p);
}else if(p instanceof bigInteger)
this.p = p;
else
this.p = undefined;
}catch(err){
log('Error when initializing modulus:', err);
throw new Error('Error: modulus is not a valid big integer');
}
/**
* @property {bigInteger.BigInteger} x - The private key of the ElGamal cryptosystem.
*/
try{
if(typeof x === 'string'){
this.x = bigInteger(x);
}else if(x instanceof bigInteger)
this.x = x;
else
this.x = undefined;
}catch(err){
log('Error when initializing private key: ', err);
throw new Error(`Private key is not a valid big integer`);
}
/**
* @property {bigInteger.BigInteger} y - The public key of ElGamal encryption
* @private
*/
try{
if(typeof y === 'string')
this.y = bigInteger(y);
else if(y instanceof bigInteger)
this.y = y;
else
this.y = undefined;
}catch(err){
log('Error when initializing public key: ', err);
throw new Error(`Public key is not a vlid big integer`);
}
/**
* @property {bigInteger.BigInteger} q - The group order
*/
if(this.p)
this.q = this.p.minus(1).divide(2);
}
/**
* Check the security of engine regarding the configured security level
* @returns {boolean} true if the engine is at given security level, otherwise false
* @throws Will throw an error if the engine is not initialized correctly.
*/
checkSecurity(){
if(this.securityLevel === 'HIGH'){
if(this.q === undefined)
if(this.p === undefined)
throw new Error('Engine is not initialized')
else
this.q = this.p.minus(1).divide(2);
else if(this.q.multiply(2).add(1).compare(this.p))
throw new Error('Inconsistent Group Order and Group Modulus');
if(! this.p.isProbablePrime())
return false;
else if(! this.q.isProbablePrime())
return false;
}else if(this.securityLevel === 'MEDIUM'){
if(this.p === undefined)
throw new Error('Engine is not initialized!');
if(! this.p.isProbablePrime())
return false;
}else
log('Warning: Low level security detected');
this.isSecure = true;
return true;
}
/**
*
* @param {securityLevel} level
* @throws Will throw an error if level is not one of allowed values
*/
setSecurityLevel(level){
if(securityLevels.indexOf(level.toUpperCase()) === -1)
throw new Error('Invalid security level');
else
this.securityLevel = level;
}
/**
* it's better to choose the lengthes which are divided evenly by 8.
* By calling this method, the engine will create a thread to find a safe prime number and
* then initialize engine using it.
* @async
* @param {number} lengthOfOrder indicate the length of Modulus of Group in bit
*/
async initialize(lengthOfOrder = 4096){
this.q = undefined;
do{
this.q = await bigIntManager.getPrime(lengthOfOrder-1);
log(this.q, typeof this.q);
this.p = this.q.shiftLeft(1).add(1);
log('one prime number produced:', this.p.bitLength());
}while(!this.p.isPrime());
//produce generator:
do{
let exponent = await bigIntManager.getInRange(this.p,3);
this.g = bigInteger[2].modPow(exponent, this.p);
}while(
this.g.modPow(this.q,this.p).notEquals(1) ||
this.g.modPow(2,this.p).equals(1) ||
this.p.prev().remainder(this.g).equals(0) ||
this.p.prev().remainder(this.g.modInv(this.p)).equals(0)
);
//Produce other parameters:
await this.fillIn();
}
/**
* This is an internale method and you shouldn't call it directly!
* Gets modulus and group order then tries to initialize ElGamal Engine.
* Also p and q should be in Safe and Sophie Germain primes form.
* @param {string|number} p The modulus of group, Should be prime.
* @param {string|number} q The order of group, Should be prime.
* @throws Will throw an error if p or q are not prime.
* @throws Will throw an error if provided p and q aren't Safe and Sophie Germain primes.
*/
async initializeUsingModulusGroup(p, q){
if(typeof p === 'number')
p = `p`
if(typeof q === 'number')
q = `q`
this.p = bigInteger(p);
this.q = bigInteger(q);
//check Validity of primes:
if(this.q.multiply(2).add(1).compareTo(this.p))
throw new Error(`The received primes are not in Safe form:
p = ${p},
q = ${q}`);
if(!this.p.isProbablePrime())
throw new Error('P is not prime: '+ p);
if(!this.q.isProbablePrime())
throw new Error('q is not prime: ' + q);
//produce generator:
do{
let exponent = await bigIntManager.getInRange(this.p,3);
this.g = bigInteger[2].modPow(exponent, this.p);
}while(
this.g.modPow(this.q, this.p).notEquals(1) ||
this.g.modPow(2,this.p).equals(1) ||
this.p.prev().remainder(this.g).equals(0) ||
this.p.prev().remainder(this.g.modInv(this.p)).equals(0)
);
//Fill in the empty parameters:
await this.fillIn();
}
/**
* it's better to choose the lengthes which are divided evenly by 8.
* This method tries to connect to a remote DB and get the underlying group information
* then initialize the engine.
* Unlike initialize() method which creates a thread and has an enormous cpu usage, this method
* don't have any cpu usage but it's need a network connection!
* @async
* @param {allowedLengthes} lengthOfOrder indicate the length of Modulus Of Group in bit
* @throws Will throw an error if there is no internet connection or received Information are
* inconsistent.
*/
async initializeRemotely(lengthOfOrder = 4096){
return new Promise((resolve, reject)=>{
https.get(`https://2ton.com.au/getprimes/random/${lengthOfOrder}`,
(res)=>{
res.on('data', async (data)=>{
let readableData = data.toString('utf8');
let primes = JSON.parse(readableData);
try{
await this.initializeUsingModulusGroup(
primes.p.base10,
primes.q.base10
)
}catch(err){
log(err)
return reject(err)
}
resolve(true);
})
}
).on('error', async (err)=>{
log(`Error: Unable to fetch group information from remote server!`)
log(err)
log(` Server Address:https://2ton.com.au/getprimes/random/${lengthOfOrder}`)
log(`Redirecting this error to console and using offline groups...`)
console.log(`Unable to connect to server: ${err}`);
//Check if we live in browser or not:
if(global?.performance?.nodeTiming?.name){
//We are in Node.js, so we can use a random group info easily.
let groupIndex = Math.floor(Math.random() * 100)
/**
* We load a random group and keep it inside memory because we may
* choose it later too:
*/
let group = require(`./offlineGroups/${lengthOfOrder}/${groupIndex}`);
try{
await this.initializeUsingModulusGroup(
group.p, group.q
)
}catch(err){
log(err);
return reject(err);
}
resolve(true)
}else{
/*
* We are in a browser, so we don't have access to file system and we should
* use default groups:
*/
if(lengthOfOrder == 2048)
await this.initializeUsingModulusGroup(
defaultGroup2048Bit.p, defaultGroup2048Bit.q
)
else if(lengthOfOrder == 3072)
await this.initializeUsingModulusGroup(
defaultGroup3072Bit.p, defaultGroup3072Bit.q
)
else if(lengthOfOrder == 4096)
await this.initializeUsingModulusGroup(
defaultGroup4096Bit.p, defaultGroup4096Bit.q
)
else if(lengthOfOrder == 8192)
await this.initializeUsingModulusGroup(
defaultGroup8192Bit.p, defaultGroup8192Bit.q
)
resolve(true);
}
})
})
}
/**
* Fill in the empty parameters of ElGamal.
* The engine should at least has the generator and modulus initialized.
* Don't use this method directly.
* @async
* @throws Will throw an Error if one of Generator or Modulus is not provided.
*/
async fillIn(){
//produce privateKey:
this.x = await bigIntManager.getInRange(
this.p.prev(),
2
);
//calculate public key:
if(this.y === undefined)
this.y = this.g.modPow(this.x, this.p);
}
/**
* Encrypt the given message under ElGamal cryptosystem schema. By default this use your own
* public key unless you change it by setting publicKey.
* @param {number|bigInteger.BigInteger} message - The message which you want to encrypt it. please note due to the ElGamal schema
* it's must be a member of underlying group.
* @returns {Promise<cipherText>} - The resulted cipher text which you can decrypt it by using decrypt() method.
* @throws Will throw an Error if the message is not a number
*/
async encrypt(message){
if(! this.isSecure){
throw new Error(`Engine is not secure to use,
please make sure you call checkSecurity() method before using engine.`);
}
if(! this.isReady()){
throw new Error(`Engine is not initialized correctly!`);
}
const tempPrivateKey = await bigIntManager.getInRange(this.p.prev(), 1);
/**
* @property {bigInteger.BigInteger} lastEncryptionKey keeps the last used encryption key.
*/
this.lastEncryptionKey = tempPrivateKey;
let msgBI;
if(typeof message === 'string'){
throw new Error('Not implemented yet! the message should of type number');
}else if(typeof message === 'number')
msgBI = bigInteger(message);
else if(message instanceof bigInteger)
msgBI = message;
else
throw new Error('Not supported message type');
log(this.y.modPow(tempPrivateKey, this.p).toString());
return {
c1 : this.g.modPow(tempPrivateKey, this.p),
c2 : this.y.modPow(tempPrivateKey, this.p).multiply(msgBI).remainder(this.p)
};
}
/**
*
* @param {cipherText} cipherPair - The result of encrypt() method.
* @returns {Promise<bigInteger.BigInteger>} - The decrypted message which is a big Integer. The message is a member of
* underlying Cyclic Group.
*/
async decrypt(cipherPair){
if(! this.isSecure){
throw new Error(`Engine is not secure to use,
please make sure you call checkSecurity() method before using engine.`);
}
if(! this.isReady()){
throw new Error(`Engine is not initialized correctly!`);
}
const sharedSecret = cipherPair.c1.modPow(this.x, this.p);
const reverseSecret = sharedSecret.modInv(this.p);
const plainText = reverseSecret.multiply(cipherPair.c2).mod(this.p);
return plainText;
}
/**
* Choose one of the underlying group members randomly!
* @returns {Promise<bigInteger.BigInteger>} One of group members which is selected randomly.
*/
async randomGropuMember(){
let exponenet = await bigIntManager.getInRange(this.p, 3);
return this.g.modPow(exponenet, this.p);
}
/**
* calculate: generator^exponent mod modulus. It works independent of ElGamal, it means you
* can use it even if ElGamal fails security conditions.
* This method is not part of elgamal but part of basic group functionality! it's provided
* here to reduce the complexity of dependencies.
* @param {bigInteger.BigInteger|string} exponent - The exponent to calculate its modular exponentiation
* regarding generator
* @returns {bigInteger.BigInteger} The resulted modular exponentiation
*/
power(exponent){
if(typeof exponent === 'string')
exponent = bigInteger(exponent);
return this.g.modPow(exponent, this.p);
}
/**
* calculate: a + b mod modulus. It works independent of ElGamal, it means you can use it
* even if ElGamal fails security conditions.
* This method is not part of elgamal but part of basic group functionality! it's provided
* here to reduce the complexity of dependencies.
* @param {bigInteger.BigInteger|string} a - The first number.
* @param {bigInteger.BigInteger|string} b - The second number to be added with a.
* @returns {bigInteger.BigInteger} - The resulted modular addition.
*/
add(a, b){
if(typeof a === 'string')
a = bigInteger(a);
if(typeof b === 'string')
b = bigInteger(b);
return a.add(b).mod(this.p);
}
/**
* calculate: a * b mod modulus. It works independent of ElGamal, it means you can use it
* even if ElGamal fails security conditions.
* This method is not part of elgamal but part of basic group functionality! it's provided
* here to reduce the complexity of dependencies.
* @param {bigInteger.BigInteger|string} a - The first number
* @param {bigInteger.BigInteger|string} b - The second number to be multiplying in a
* @returns {bigInteger.BigInteger} - The resulted modular multiplication.
*/
multiply(a, b){
if(typeof a === 'string')
a = bigInteger(a);
if(typeof b === 'string')
b = bigInteger(b);
return a.multiply(b).mod(this.p);
}
/**
* @param {bigInteger.BigInteger|string} _g Generator
*/
set generator(_g){
if(typeof _g === 'string')
_g = bigInteger(_g);
this.g = _g;
}
/**
* @type {string}
*/
get generator(){
return this.g.toString();
}
/**
* @param {bigInteger.BigInteger|string} _q Order of inner cyclic group
*/
set groupOrder(_q){
if(typeof _q === 'string')
_q = bigInteger(_q);
this.q = _q;
}
/**
* @type {string}
*/
get groupOrder(){
return this.q.toString();
}
/**
* @param {bigInteger.BigInteger|string} _p Modulus of Multiplicative group
*/
set modulus(_p){
if(typeof _p === 'string')
_p = bigInteger(_p);
this.p = _p;
}
/**
* @type {string}
*/
get modulus(){
return this.p.toString();
}
/**
* This is not ElGamal public key but the public secret.
* @param {bigInteger.BigInteger|string} _y public key
*/
set publicKey(_y){
if(typeof _y === 'string')
_y = bigInteger(_y);
this.y = _y;
}
/**
* @type {string}
*/
get publicKey(){
return this.y.toString();
}
/**
* We strongly advise to avoid calling this method!
* @param {bigInteger.BigInteger|string} _x private key
*
*/
set privateKey(_x){
if(typeof _x === 'string')
_x = bigInteger(_x);
this.x = _x;
}
/**
* Will return the private key if and only if the securityLevel is 'LOW'.
* We strongly advise to avoid calling this method,
* because this method return the flat private key and not
* ElGamal private key
* @type {string?}
* @throws Will throw an Error if securityLevel is not 'LOW'
*/
get privateKey(){
if(this.securityLevel === 'LOW')
return this.x.toString();
else
throw new Error('Violating security policies. First set the security level to \'LOW\'');
}
/**
* This method checks if engine is initialized or not. It doesn't check engine security.
* @returns {boolean} - True if all parameters of engine are available and false otherwise.
*/
isReady(){
if(this.p && this.g && this.y && this.x){
return true;
}
return false;
}
/**
* This method will export the ElGamal engine so that you can build it again completely.
* @param {boolean} deep if true then some last secret key will be revealed and removed from
* ElGamal for security sake.
* @returns {Engine} The exported ElGamal engine, you can import it easily by calling import() method
*/
export(deep){
let r = undefined;
if(deep){
r = this.lastEncryptionKey;
this.lastEncryptionKey = undefined;
}
return {
r,
security: this.securityLevel,
...(this.securityLevel === 'LOW'? {x: this.x} : undefined),
g: this.g,
p: this.p,
y: this.g.modPow(this.x, this.p)
};
}
/**
* This method will import ElGamal engine based on passed info.
* @param {Engine} engine - The ElGamal info which is exported using export() method.
*/
import(engine){
this.isSecure = false;
this.g = engine.g;
this.p = engine.p;
this.y = engine.p;
this.securityLevel = engine.security;
this.x = engine.x;
}
}
/**
* ElGamal Engine
*/
module.exports = ElGamal;