229 lines
6.6 KiB
JavaScript
229 lines
6.6 KiB
JavaScript
|
|
/**
|
||
|
|
* @preserve Copyright (c) 2014-2015 IONU Security, Inc. All rights reserved.
|
||
|
|
*
|
||
|
|
* @file EyeDRBG methods for deterministic random bit generation and predictable RSA keys
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
IONU_DRBG_BUFFER_LEN = 32768;
|
||
|
|
|
||
|
|
if(typeof BigInteger === 'undefined') {
|
||
|
|
var BigInteger = forge.jsbn.BigInteger;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructor for DRBG object
|
||
|
|
* @constructor
|
||
|
|
* @param {string} seed value
|
||
|
|
*/
|
||
|
|
function EyeDRBG (seed) {
|
||
|
|
if (!(this instanceof EyeDRBG)){
|
||
|
|
return new EyeDRBG (seed);
|
||
|
|
}
|
||
|
|
this.seed = seed;
|
||
|
|
this.ctr = seed.length & 255;
|
||
|
|
this.buffer = '';
|
||
|
|
this.pos = 0;
|
||
|
|
var md = forge.md.sha256.create();
|
||
|
|
md.update(seed);
|
||
|
|
this.hashbuf = md.digest().getBytes();
|
||
|
|
//console.log (forge.util.bytesToHex(this.hashbuf));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Define methods for EyeDRBG object
|
||
|
|
EyeDRBG.prototype = {
|
||
|
|
constructor: EyeDRBG,
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Fill the buffer with pseudo random bytes
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
fillBuffer: function() {
|
||
|
|
const BUFF_SIZE = IONU_DRBG_BUFFER_LEN;
|
||
|
|
this.buffer = '';
|
||
|
|
this.pos = 0;
|
||
|
|
var msg = '';
|
||
|
|
var md = forge.md.sha256.create();
|
||
|
|
for (var i = 0; i < BUFF_SIZE; i += 32) {
|
||
|
|
msg = '';
|
||
|
|
md.start();
|
||
|
|
for (var j = 0; j < 32; ++j) {
|
||
|
|
msg += String.fromCharCode (this.hashbuf.charCodeAt(j));
|
||
|
|
}
|
||
|
|
msg += String.fromCharCode(this.ctr++ & 255);
|
||
|
|
md.update(msg);
|
||
|
|
this.hashbuf = md.digest().getBytes();
|
||
|
|
this.buffer += this.hashbuf;
|
||
|
|
//console.log (forge.util.bytesToHex(this.hashbuf));
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the specifed number of bytes
|
||
|
|
* @param {number} count bytes to get
|
||
|
|
* @return {string} byte string
|
||
|
|
*/
|
||
|
|
randBytes: function (count) {
|
||
|
|
var bytes = '';
|
||
|
|
var bytesavailable = this.buffer.length - this.pos;
|
||
|
|
// We have all the bytes needed
|
||
|
|
if (bytesavailable >= count) {
|
||
|
|
bytes = this.buffer.slice(this.pos, this.pos + count);
|
||
|
|
this.pos += count;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
// Copy any remaining bytes
|
||
|
|
if (bytesavailable > 0) {
|
||
|
|
count -= bytesavailable;
|
||
|
|
bytes = this.buffer.slice (this.pos, bytesavailable);
|
||
|
|
}
|
||
|
|
// Loop until we've generated all the required bytes
|
||
|
|
while (count > 0) {
|
||
|
|
this.fillBuffer ();
|
||
|
|
bytesavailable = this.buffer.length - this.pos;
|
||
|
|
if (bytesavailable >= count ) {
|
||
|
|
bytes += this.buffer.slice(this.pos, this.pos + count);
|
||
|
|
this.pos += count;
|
||
|
|
count = 0;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
count -= bytesavailable;
|
||
|
|
bytes += this.buffer.slice (this.pos, bytesavailable);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return bytes;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get an integer with the specifed number of bits
|
||
|
|
* @param {number} bits number of bits to get
|
||
|
|
* @return {number} integer of specified number of bits
|
||
|
|
*/
|
||
|
|
randBits: function (bits) {
|
||
|
|
var rval = 0;
|
||
|
|
var numBytes = Math.floor ((bits + 7) / 8);
|
||
|
|
|
||
|
|
for (i = 0; i < numBytes; i++) {
|
||
|
|
rval = (rval << 8) | this.randBytes (1);
|
||
|
|
}
|
||
|
|
return rval & (1 << bits);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get an integer from 0 to the specifed maximum
|
||
|
|
* @param {number} maximum largest integer to get
|
||
|
|
* @return {number} integer between 0 and maximum, inclusive
|
||
|
|
*/
|
||
|
|
randInteger: function (maximum) {
|
||
|
|
var n = 1, numBits = 0;
|
||
|
|
|
||
|
|
// numBits = log_2 n, where n >= maximum
|
||
|
|
while (maximum >= n) {
|
||
|
|
if (numBits > 0) {
|
||
|
|
n <<= 1;
|
||
|
|
}
|
||
|
|
numBits++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate random values from 0 through n, then discard any values where value > maximum.
|
||
|
|
// See Ferguson and Schneier, "Practical Cryptography" for why this is better than using mod
|
||
|
|
while (true) {
|
||
|
|
var value = this.randBits (numBits);
|
||
|
|
if (value <= n) {
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate pseudo prime suitable for RSA key
|
||
|
|
* @param {BigInteger} exponent public exponent (65,537)
|
||
|
|
* @return {BigInteger} prime number
|
||
|
|
*/
|
||
|
|
generateRSAPrime: function (e) {
|
||
|
|
// Only works for IONU_RSA_KEY_BITS size keys, prime is IONU_RSA_KEY_BITS / 2
|
||
|
|
var bytes = IONU_RSA_KEY_BITS / 16;
|
||
|
|
var op_or = function(x,y) { return x|y; };
|
||
|
|
var THIRTY = new BigInteger(null);
|
||
|
|
THIRTY.fromInt(30);
|
||
|
|
var iterations = 0;
|
||
|
|
while (true) {
|
||
|
|
buff = this.randBytes (bytes); // Get bytes from our DRBG
|
||
|
|
var hex = forge.util.bytesToHex(buff);
|
||
|
|
var prime = new BigInteger (hex, 16);
|
||
|
|
|
||
|
|
// Set 2 most significant bits so p * q is exactly n bits, and least to ensure odd
|
||
|
|
var bits = IONU_RSA_KEY_BITS / 2 - 1;
|
||
|
|
prime.bitwiseTo (BigInteger.ONE.shiftLeft(bits), op_or, prime);
|
||
|
|
prime.bitwiseTo (BigInteger.ONE.shiftLeft(bits-1), op_or, prime);
|
||
|
|
prime.bitwiseTo (BigInteger.ONE, op_or, prime);
|
||
|
|
if (++iterations >= 2000) {
|
||
|
|
console.log ('prime failed in 2000 iterations');
|
||
|
|
return prime;
|
||
|
|
}
|
||
|
|
// Ensure number is coprime with e
|
||
|
|
if (prime.subtract(BigInteger.ONE).gcd(e).compareTo(BigInteger.ONE) === 0) {
|
||
|
|
// Check to see if it is prime
|
||
|
|
if (prime.isProbablePrime(2)) {
|
||
|
|
//console.log ('prime in ' + iterations + ' iterations');
|
||
|
|
return prime;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate deterministic RSA key
|
||
|
|
* @return {Key}
|
||
|
|
*/
|
||
|
|
generateRSAKey: function () {
|
||
|
|
// Set up public exponent
|
||
|
|
var eInt = IONU_RSA_PUBLIC_EXP;
|
||
|
|
var e = new BigInteger(null);
|
||
|
|
e.fromInt(eInt);
|
||
|
|
|
||
|
|
// Generate random primes suitable for RSA keys, proper size, odd numbers and
|
||
|
|
// checking for coprime with e is done in generateRSAPrime.
|
||
|
|
var p = this.generateRSAPrime (e);
|
||
|
|
var q = this.generateRSAPrime (e);
|
||
|
|
while (p.compareTo (q) === 0) { // Just is case they are equal
|
||
|
|
q = this.generateRSAPrime (e);
|
||
|
|
if (p.compareTo (q) === 0) { // Equal again, it's a bug, don't hang
|
||
|
|
console.log ('generateRSAKey() - generated the same p and q');
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ensure p is larger than q (swap them if not for performance)
|
||
|
|
if(p.compareTo(q) < 0) {
|
||
|
|
var num = p;
|
||
|
|
p = q;
|
||
|
|
q = num;
|
||
|
|
}
|
||
|
|
// Compute phi: (p - 1)(q - 1) (Euler's totient function)
|
||
|
|
var p1 = p.subtract(BigInteger.ONE);
|
||
|
|
var q1 = q.subtract(BigInteger.ONE);
|
||
|
|
var phi = p1.multiply(q1);
|
||
|
|
|
||
|
|
// Ensure e and phi are coprime, ignore this test as OpenSSL does not use it
|
||
|
|
if(phi.gcd(e).compareTo(BigInteger.ONE) !== 0) {
|
||
|
|
console.log ('generateRSAKey() - phi and e are not coprime');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create n, and ensure it has the right number of bits
|
||
|
|
var n = p.multiply(q);
|
||
|
|
if (n.bitLength() !== IONU_RSA_KEY_BITS) {
|
||
|
|
console.log ('generateRSAKey() - invalid bit length ' + n.bitLength());
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
// set keys
|
||
|
|
var d = e.modInverse(phi);
|
||
|
|
var key = {
|
||
|
|
privateKey: forge.pki.rsa.setPrivateKey (n, e, d, p, q, d.mod(p1), d.mod(q1), q.modInverse(p)),
|
||
|
|
publicKey: forge.pki.rsa.setPublicKey (n, e)
|
||
|
|
};
|
||
|
|
return key;
|
||
|
|
}
|
||
|
|
};
|