fork-openpgpjs/src/crypto/public_key/rsa.js
2018-02-28 15:49:41 -08:00

229 lines
7.1 KiB
JavaScript

// GPG4Browsers - An OpenPGP implementation in javascript
// Copyright (C) 2011 Recurity Labs GmbH
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3.0 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// RSA implementation
/**
* @requires bn.js
* @requires crypto/public_key/prime
* @requires crypto/random
* @requires config
* @requires util
* @module crypto/public_key/rsa
*/
import BN from 'bn.js';
import prime from './prime';
import random from '../random';
import config from '../../config';
import util from '../../util';
// Helper for IE11 KeyOperation objects
function promisifyIE11Op(keyObj, err) {
if (typeof keyObj.then !== 'function') { // IE11 KeyOperation
return new Promise(function(resolve, reject) {
keyObj.onerror = function () {
reject(new Error(err));
};
keyObj.oncomplete = function (e) {
resolve(e.target.result);
};
});
}
return keyObj;
}
export default {
/** Create signature
* @param m message as BN
* @param n public MPI part as BN
* @param e public MPI part as BN
* @param d private MPI part as BN
* @return BN
*/
sign: function(m, n, e, d) {
if (n.cmp(m) <= 0) {
throw new Error('Data too large.');
}
const nred = new BN.red(n);
return m.toRed(nred).redPow(d).toArrayLike(Uint8Array, 'be', n.byteLength());
},
/**
* Verify signature
* @param s signature as BN
* @param n public MPI part as BN
* @param e public MPI part as BN
* @return BN
*/
verify: function(s, n, e) {
if (n.cmp(s) <= 0) {
throw new Error('Data too large.');
}
const nred = new BN.red(n);
return s.toRed(nred).redPow(e).toArrayLike(Uint8Array, 'be', n.byteLength());
},
/**
* Encrypt message
* @param m message as BN
* @param n public MPI part as BN
* @param e public MPI part as BN
* @return BN
*/
encrypt: function(m, n, e) {
if (n.cmp(m) <= 0) {
throw new Error('Data too large.');
}
const nred = new BN.red(n);
return m.toRed(nred).redPow(e).toArrayLike(Uint8Array, 'be', n.byteLength());
},
/**
* Decrypt RSA message
* @param m message as BN
* @param n RSA public modulus n as BN
* @param e RSA public exponent as BN
* @param d RSA d as BN
* @param p RSA p as BN
* @param q RSA q as BN
* @param u RSA u as BN
* @return {BN} The decrypted value of the message
*/
decrypt: function(m, n, e, d, p, q, u) {
if (n.cmp(m) <= 0) {
throw new Error('Data too large.');
}
const dq = d.mod(q.subn(1)); // d mod (q-1)
const dp = d.mod(p.subn(1)); // d mod (p-1)
const pred = new BN.red(p);
const qred = new BN.red(q);
const nred = new BN.red(n);
let blinder;
let unblinder;
if (config.rsa_blinding) {
unblinder = random.getRandomBN(new BN(2), n).toRed(nred);
blinder = unblinder.redInvm().redPow(e);
m = m.toRed(nred).redMul(blinder).fromRed();
}
const mp = m.toRed(pred).redPow(dp);
const mq = m.toRed(qred).redPow(dq);
const t = mq.redSub(mp.fromRed().toRed(qred));
const h = u.toRed(qred).redMul(t).fromRed();
let result = h.mul(p).add(mp).toRed(nred);
if (config.rsa_blinding) {
result = result.redMul(unblinder);
}
return result.toArrayLike(Uint8Array, 'be', n.byteLength());
},
/**
* Generate a new random private key B bits long with public exponent E
* @param {Integer} B RSA bit length
* @param {String} E RSA public exponent in hex string
* @return {{n: BN, e: BN, d: BN,
p: BN, q: BN, u: BN}} RSA public modulus, RSA public exponent, RSA private exponent,
RSA private prime p, RSA private prime q, u = q ** -1 mod p
*/
generate: async function(B, E) {
let key;
E = new BN(E, 16);
const webCrypto = util.getWebCryptoAll();
// Native RSA keygen using Web Crypto
if (webCrypto) {
let keyPair;
let keyGenOpt;
if ((window.crypto && window.crypto.subtle) || window.msCrypto) {
// current standard spec
keyGenOpt = {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: B, // the specified keysize in bits
publicExponent: E.toArrayLike(Uint8Array), // take three bytes (max 65537) for exponent
hash: {
name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify'
}
};
keyPair = webCrypto.generateKey(keyGenOpt, true, ['sign', 'verify']);
keyPair = await promisifyIE11Op(keyPair, 'Error generating RSA key pair.');
} else if (window.crypto && window.crypto.webkitSubtle) {
// outdated spec implemented by old Webkit
keyGenOpt = {
name: 'RSA-OAEP',
modulusLength: B, // the specified keysize in bits
publicExponent: E.toArrayLike(Uint8Array), // take three bytes (max 65537) for exponent
hash: {
name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify'
}
};
keyPair = await webCrypto.generateKey(keyGenOpt, true, ['encrypt', 'decrypt']);
} else {
throw new Error('Unknown WebCrypto implementation');
}
// export the generated keys as JsonWebKey (JWK)
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33
let jwk = webCrypto.exportKey('jwk', keyPair.privateKey);
jwk = await promisifyIE11Op(jwk, 'Error exporting RSA key pair.');
// parse raw ArrayBuffer bytes to jwk/json (WebKit/Safari/IE11 quirk)
if (jwk instanceof ArrayBuffer) {
jwk = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(jwk)));
}
// map JWK parameters to BN
key = {};
key.n = new BN(util.b64_to_Uint8Array(jwk.n));
key.e = E;
key.d = new BN(util.b64_to_Uint8Array(jwk.d));
key.p = new BN(util.b64_to_Uint8Array(jwk.p));
key.q = new BN(util.b64_to_Uint8Array(jwk.q));
key.u = key.p.invm(key.q);
return key;
}
// RSA keygen fallback using 40 iterations of the Miller-Rabin test
// See https://stackoverflow.com/a/6330138 for justification
// Also see section C.3 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST
let p = prime.randomProbablePrime(B - (B >> 1), E, 40);
let q = prime.randomProbablePrime(B >> 1, E, 40);
if (p.cmp(q) < 0) {
[p, q] = [q, p];
}
const phi = p.subn(1).mul(q.subn(1));
return {
n: p.mul(q),
e: E,
d: E.invm(phi),
p: p,
q: q,
// dp: d.mod(p.subn(1)),
// dq: d.mod(q.subn(1)),
u: p.invm(q)
};
},
prime: prime
};