diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index d4526020..72680111 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -280,7 +280,7 @@ export default { return constructParams(types, [keyObject.oid, keyObject.Q, [keyObject.hash, keyObject.cipher], keyObject.d]); }); default: - throw new Error('Invalid public key encryption algorithm.'); + throw new Error('Invalid public key algorithm.'); } }, diff --git a/src/crypto/public_key/prime.js b/src/crypto/public_key/prime.js index a35743b9..6165ee8e 100644 --- a/src/crypto/public_key/prime.js +++ b/src/crypto/public_key/prime.js @@ -15,7 +15,7 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -// Algorithms for probabilistic prime generation +// Algorithms for probabilistic random prime generation /** * @requires bn.js @@ -30,7 +30,14 @@ export default { randomProbablePrime, isProbablePrime, fermat, millerRabin }; -function randomProbablePrime(bits, e) { +/** + * Probabilistic random number generator + * @param {Integer} bits Bit length of the prime + * @param {BN} e Optional RSA exponent to check against the prime + * @param {Integer} k Optional number of iterations of Miller-Rabin test + * @return BN + */ +function randomProbablePrime(bits, e, k) { const min = new BN(1).shln(bits - 1); let n = random.getRandomBN(min, min.shln(1)); @@ -38,7 +45,7 @@ function randomProbablePrime(bits, e) { n.iaddn(1); // force odd } - while (!isProbablePrime(n, e)) { + while (!isProbablePrime(n, e, k)) { n.iaddn(2); // If reached the maximum, go back to the minimum. if (n.bitLength() > bits) { @@ -48,14 +55,21 @@ function randomProbablePrime(bits, e) { return n; } -function isProbablePrime(n, e) { +/** + * Probabilistic primality testing + * @param {BN} n Number to test + * @param {BN} e Optional RSA exponent to check against the prime + * @param {Integer} k Optional number of iterations of Miller-Rabin test + * @return {boolean} + */ +function isProbablePrime(n, e, k) { if (e && !n.subn(1).gcd(e).eqn(1)) { return false; } if (!fermat(n)) { return false; } - if (!millerRabin(n)) { + if (!millerRabin(n, k)) { return false; } return true; @@ -64,6 +78,9 @@ function isProbablePrime(n, e) { /** * Tests whether n is probably prime or not using Fermat's test with b = 2. * Fails if b^(n-1) mod n === 1. + * @param {BN} n Number to test + * @param {Integer} b Optional Fermat test base + * @return {boolean} */ function fermat(n, b) { b = b || new BN(2); @@ -103,33 +120,38 @@ function fermat(n, b) { /** * Tests whether n is probably prime or not using the Miller-Rabin test. * See HAC Remark 4.28. + * @param {BN} n Number to test + * @param {Integer} k Optional number of iterations of Miller-Rabin test + * @param {Function} cb Optional callback function to call with random witnesses + * @return {boolean} */ function millerRabin(n, k, cb) { - var len = n.bitLength(); - var red = BN.mont(n); - var rone = new BN(1).toRed(red); + const len = n.bitLength(); + const red = BN.mont(n); + const rone = new BN(1).toRed(red); if (!k) k = Math.max(1, (len / 48) | 0); // Find d and s, (n - 1) = (2 ^ s) * d; - var n1 = n.subn(1); - for (var s = 0; !n1.testn(s); s++) {} - var d = n.shrn(s); + const n1 = n.subn(1); + let s = 0; + while (!n1.testn(s)) { s++; } + const d = n.shrn(s); - var rn1 = n1.toRed(red); + const rn1 = n1.toRed(red); - var prime = true; for (; k > 0; k--) { - var a = random.getRandomBN(new BN(2), n1); + let a = random.getRandomBN(new BN(2), n1); if (cb) cb(a); - var x = a.toRed(red).redPow(d); + let x = a.toRed(red).redPow(d); if (x.cmp(rone) === 0 || x.cmp(rn1) === 0) continue; - for (var i = 1; i < s; i++) { + let i; + for (i = 1; i < s; i++) { x = x.redSqr(); if (x.cmp(rone) === 0) @@ -142,5 +164,5 @@ function millerRabin(n, k, cb) { return false; } - return prime; + return true; }; diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 4cf30784..146a2461 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -19,7 +19,6 @@ /** * @requires bn.js - * @requires asmcrypto.js * @requires crypto/public_key/prime * @requires crypto/random * @requires config @@ -135,7 +134,10 @@ export default { /** * 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 + * @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; @@ -193,13 +195,13 @@ export default { } while (true) { - let p = prime.randomProbablePrime(B - (B >> 1), E); - let q = prime.randomProbablePrime(B >> 1, E); + // 40 iterations of the Miller-Rabin test + // See https://stackoverflow.com/a/6330138 for justification + let p = prime.randomProbablePrime(B - (B >> 1), E, 40); + let q = prime.randomProbablePrime(B >> 1, E, 40); if (p.cmp(q) < 0) { - const t = p; - p = q; - q = t; + [p, q] = [q, p]; } const phi = p.subn(1).mul(q.subn(1)); @@ -214,5 +216,7 @@ export default { u: p.invm(q) }; } - } + }, + + prime: prime }; diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 56dc2a6c..c77ef82b 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -1,5 +1,4 @@ const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const AES_CFB = require('asmcrypto.js/asmcrypto.all.js').AES_CFB; const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -297,11 +296,7 @@ describe('API functional testing', function() { const prefix = util.concatUint8Array([rndm, repeat]); const symmencData = crypto.cfb.encrypt(rndm, algo, util.str2Uint8Array(plaintext), symmKey, false); - const symmencData2 = AES_CFB.encrypt(util.concatUint8Array([prefix, util.str2Uint8Array(plaintext)]), symmKey); - - let decrypted = AES_CFB.decrypt(symmencData, symmKey); - decrypted = decrypted.subarray(crypto.cipher[algo].blockSize + 2, decrypted.length); - expect(util.Uint8Array2str(symmencData)).to.equal(util.Uint8Array2str(symmencData2)); + const decrypted = crypto.cfb.decrypt(algo, symmKey, symmencData, false); const text = util.Uint8Array2str(decrypted); expect(text).to.equal(plaintext);