UNPKG

@hchuanz/aes-forge

Version:
436 lines (368 loc) 13 kB
var forge = require('./forge'); require('./asn1'); require('./util'); require('./random'); require('./jsbn'); if(typeof BigInteger === 'undefined') { var BigInteger = forge.jsbn.BigInteger; } // shortcut for asn.1 API var asn1 = forge.asn1; // shortcut for util API var util = forge.util; /* * RSA encryption and decryption, see RFC 2313. */ forge.pki = forge.pki || {}; module.exports = forge.pki.rsa = forge.rsa = forge.rsa || {}; var pki = forge.pki; pki.privateKeyFromAsn1 = function(obj) { // get PrivateKeyInfo var capture = {}; var errors = []; if(asn1.validate(obj, privateKeyValidator, capture, errors)) { obj = asn1.fromDer(forge.util.createBuffer(capture.privateKey)); } // get RSAPrivateKey capture = {}; errors = []; if(!asn1.validate(obj, rsaPrivateKeyValidator, capture, errors)) { var error = new Error('Cannot read private key. ' + 'ASN.1 object does not contain an RSAPrivateKey.'); error.errors = errors; throw error; } // Note: Version is currently ignored. // capture.privateKeyVersion // FIXME: inefficient, get a BigInteger that uses byte strings var n, e, d, p, q, dP, dQ, qInv; n = forge.util.createBuffer(capture.privateKeyModulus).toHex(); e = forge.util.createBuffer(capture.privateKeyPublicExponent).toHex(); d = forge.util.createBuffer(capture.privateKeyPrivateExponent).toHex(); p = forge.util.createBuffer(capture.privateKeyPrime1).toHex(); q = forge.util.createBuffer(capture.privateKeyPrime2).toHex(); dP = forge.util.createBuffer(capture.privateKeyExponent1).toHex(); dQ = forge.util.createBuffer(capture.privateKeyExponent2).toHex(); qInv = forge.util.createBuffer(capture.privateKeyCoefficient).toHex(); // set private key return pki.setRsaPrivateKey( new BigInteger(n, 16), new BigInteger(e, 16), new BigInteger(d, 16), new BigInteger(p, 16), new BigInteger(q, 16), new BigInteger(dP, 16), new BigInteger(dQ, 16), new BigInteger(qInv, 16)); }; pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { var key = { n: n, e: e }; key.encrypt = function(data, scheme, schemeOptions) { if(typeof scheme === 'string') { scheme = scheme.toUpperCase(); } else if(scheme === undefined) { scheme = 'RSAES-PKCS1-V1_5'; } if(scheme === 'RSAES-PKCS1-V1_5') { scheme = { encode: function(m, key, pub) { return _encodePkcs1_v1_5(m, key, 0x02).getBytes(); } }; } else if(scheme === 'RSA-OAEP' || scheme === 'RSAES-OAEP') { scheme = { encode: function(m, key) { return forge.pkcs1.encode_rsa_oaep(key, m, schemeOptions); } }; } else if(['RAW', 'NONE', 'NULL', null].indexOf(scheme) !== -1) { scheme = {encode: function(e) {return e;}}; } else if(typeof scheme === 'string') { throw new Error('Unsupported encryption scheme: "' + scheme + '".'); } // do scheme-based encoding then rsa encryption var e = scheme.encode(data, key, true); return pki.rsa.encrypt(e, key, true); }; return key; }; pki.rsa.encrypt = function(m, key, bt) { var pub = bt; var eb; // get the length of the modulus in bytes var k = Math.ceil(key.n.bitLength() / 8); if(bt !== false && bt !== true) { // legacy, default to PKCS#1 v1.5 padding pub = (bt === 0x02); eb = _encodePkcs1_v1_5(m, key, bt); } else { eb = forge.util.createBuffer(); eb.putBytes(m); } // load encryption block as big integer 'x' // FIXME: hex conversion inefficient, get BigInteger w/byte strings var x = new BigInteger(eb.toHex(), 16); // do RSA encryption var y = _modPow(x, key, pub); // convert y into the encrypted data byte string, if y is shorter in // bytes than k, then prepend zero bytes to fill up ed // FIXME: hex conversion inefficient, get BigInteger w/byte strings var yhex = y.toString(16); var ed = forge.util.createBuffer(); var zeros = k - Math.ceil(yhex.length / 2); while(zeros > 0) { ed.putByte(0x00); --zeros; } ed.putBytes(forge.util.hexToBytes(yhex)); return ed.getBytes(); }; function _encodePkcs1_v1_5(m, key, bt) { var eb = forge.util.createBuffer(); // get the length of the modulus in bytes var k = Math.ceil(key.n.bitLength() / 8); /* use PKCS#1 v1.5 padding */ if(m.length > (k - 11)) { var error = new Error('Message is too long for PKCS#1 v1.5 padding.'); error.length = m.length; error.max = k - 11; throw error; } /* A block type BT, a padding string PS, and the data D shall be formatted into an octet string EB, the encryption block: EB = 00 || BT || PS || 00 || D The block type BT shall be a single octet indicating the structure of the encryption block. For this version of the document it shall have value 00, 01, or 02. For a private-key operation, the block type shall be 00 or 01. For a public-key operation, it shall be 02. The padding string PS shall consist of k-3-||D|| octets. For block type 00, the octets shall have value 00; for block type 01, they shall have value FF; and for block type 02, they shall be pseudorandomly generated and nonzero. This makes the length of the encryption block EB equal to k. */ // build the encryption block eb.putByte(0x00); eb.putByte(bt); // create the padding var padNum = k - 3 - m.length; var padByte; // private key op if(bt === 0x00 || bt === 0x01) { padByte = (bt === 0x00) ? 0x00 : 0xFF; for(var i = 0; i < padNum; ++i) { eb.putByte(padByte); } } else { // public key op // pad with random non-zero values while(padNum > 0) { var numZeros = 0; var padBytes = forge.random.getBytes(padNum); for(var i = 0; i < padNum; ++i) { padByte = padBytes.charCodeAt(i); if(padByte === 0) { ++numZeros; } else { eb.putByte(padByte); } } padNum = numZeros; } } // zero followed by message eb.putByte(0x00); eb.putBytes(m); return eb; } var rsaPublicKeyValidator = { // RSAPublicKey name: 'RSAPublicKey', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ // modulus (n) name: 'RSAPublicKey.modulus', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.INTEGER, constructed: false, capture: 'publicKeyModulus' }, { // publicExponent (e) name: 'RSAPublicKey.exponent', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.INTEGER, constructed: false, capture: 'publicKeyExponent' }] }; var publicKeyValidator = forge.pki.rsa.publicKeyValidator = { name: 'SubjectPublicKeyInfo', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'subjectPublicKeyInfo', value: [{ name: 'SubjectPublicKeyInfo.AlgorithmIdentifier', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ name: 'AlgorithmIdentifier.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'publicKeyOid' }] }, { // subjectPublicKey name: 'SubjectPublicKeyInfo.subjectPublicKey', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.BITSTRING, constructed: false, value: [{ // RSAPublicKey name: 'SubjectPublicKeyInfo.subjectPublicKey.RSAPublicKey', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, optional: true, captureAsn1: 'rsaPublicKey' }] }] }; pki.publicKeyFromAsn1 = function(obj) { // get SubjectPublicKeyInfo var capture = {}; var errors = []; if(asn1.validate(obj, publicKeyValidator, capture, errors)) { // get oid var oid = asn1.derToOid(capture.publicKeyOid); if(oid !== pki.oids.rsaEncryption) { var error = new Error('Cannot read public key. Unknown OID.'); error.oid = oid; throw error; } obj = capture.rsaPublicKey; } // get RSA params errors = []; if(!asn1.validate(obj, rsaPublicKeyValidator, capture, errors)) { var error = new Error('Cannot read public key. ' + 'ASN.1 object does not contain an RSAPublicKey.'); error.errors = errors; throw error; } // FIXME: inefficient, get a BigInteger that uses byte strings var n = forge.util.createBuffer(capture.publicKeyModulus).toHex(); var e = forge.util.createBuffer(capture.publicKeyExponent).toHex(); // set public key return pki.setRsaPublicKey( new BigInteger(n, 16), new BigInteger(e, 16)); }; var _modPow = function(x, key, pub) { if(pub) { return x.modPow(key.e, key.n); } if(!key.p || !key.q) { // allow calculation without CRT params (slow) return x.modPow(key.d, key.n); } // pre-compute dP, dQ, and qInv if necessary if(!key.dP) { key.dP = key.d.mod(key.p.subtract(BigInteger.ONE)); } if(!key.dQ) { key.dQ = key.d.mod(key.q.subtract(BigInteger.ONE)); } if(!key.qInv) { key.qInv = key.q.modInverse(key.p); } /* Chinese remainder theorem (CRT) states: Suppose n1, n2, ..., nk are positive integers which are pairwise coprime (n1 and n2 have no common factors other than 1). For any integers x1, x2, ..., xk there exists an integer x solving the system of simultaneous congruences (where ~= means modularly congruent so a ~= b mod n means a mod n = b mod n): x ~= x1 mod n1 x ~= x2 mod n2 ... x ~= xk mod nk This system of congruences has a single simultaneous solution x between 0 and n - 1. Furthermore, each xk solution and x itself is congruent modulo the product n = n1*n2*...*nk. So x1 mod n = x2 mod n = xk mod n = x mod n. The single simultaneous solution x can be solved with the following equation: x = sum(xi*ri*si) mod n where ri = n/ni and si = ri^-1 mod ni. Where x is less than n, xi = x mod ni. For RSA we are only concerned with k = 2. The modulus n = pq, where p and q are coprime. The RSA decryption algorithm is: y = x^d mod n Given the above: x1 = x^d mod p r1 = n/p = q s1 = q^-1 mod p x2 = x^d mod q r2 = n/q = p s2 = p^-1 mod q So y = (x1r1s1 + x2r2s2) mod n = ((x^d mod p)q(q^-1 mod p) + (x^d mod q)p(p^-1 mod q)) mod n According to Fermat's Little Theorem, if the modulus P is prime, for any integer A not evenly divisible by P, A^(P-1) ~= 1 mod P. Since A is not divisible by P it follows that if: N ~= M mod (P - 1), then A^N mod P = A^M mod P. Therefore: A^N mod P = A^(M mod (P - 1)) mod P. (The latter takes less effort to calculate). In order to calculate x^d mod p more quickly the exponent d mod (p - 1) is stored in the RSA private key (the same is done for x^d mod q). These values are referred to as dP and dQ respectively. Therefore we now have: y = ((x^dP mod p)q(q^-1 mod p) + (x^dQ mod q)p(p^-1 mod q)) mod n Since we'll be reducing x^dP by modulo p (same for q) we can also reduce x by p (and q respectively) before hand. Therefore, let xp = ((x mod p)^dP mod p), and xq = ((x mod q)^dQ mod q), yielding: y = (xp*q*(q^-1 mod p) + xq*p*(p^-1 mod q)) mod n This can be further reduced to a simple algorithm that only requires 1 inverse (the q inverse is used) to be used and stored. The algorithm is called Garner's algorithm. If qInv is the inverse of q, we simply calculate: y = (qInv*(xp - xq) mod p) * q + xq However, there are two further complications. First, we need to ensure that xp > xq to prevent signed BigIntegers from being used so we add p until this is true (since we will be mod'ing with p anyway). Then, there is a known timing attack on algorithms using the CRT. To mitigate this risk, "cryptographic blinding" should be used. This requires simply generating a random number r between 0 and n-1 and its inverse and multiplying x by r^e before calculating y and then multiplying y by r^-1 afterwards. Note that r must be coprime with n (gcd(r, n) === 1) in order to have an inverse. */ // cryptographic blinding var r; do { r = new BigInteger( forge.util.bytesToHex(forge.random.getBytes(key.n.bitLength() / 8)), 16); } while(r.compareTo(key.n) >= 0 || !r.gcd(key.n).equals(BigInteger.ONE)); x = x.multiply(r.modPow(key.e, key.n)).mod(key.n); // calculate xp and xq var xp = x.mod(key.p).modPow(key.dP, key.p); var xq = x.mod(key.q).modPow(key.dQ, key.q); // xp must be larger than xq to avoid signed bit usage while(xp.compareTo(xq) < 0) { xp = xp.add(key.p); } // do last step var y = xp.subtract(xq) .multiply(key.qInv).mod(key.p) .multiply(key.q).add(xq); // remove effect of random for cryptographic blinding y = y.multiply(r.modInverse(key.n)).mod(key.n); return y; };