diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 6ab75b47..47873961 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -41,7 +41,6 @@ import type_mpi from '../type/mpi'; import type_oid from '../type/oid'; import util from '../util'; - function constructParams(types, data) { return types.map(function(type, i) { if (data && data[i]) { diff --git a/src/crypto/public_key/dsa.js b/src/crypto/public_key/dsa.js index 1fb64979..8d488e38 100644 --- a/src/crypto/public_key/dsa.js +++ b/src/crypto/public_key/dsa.js @@ -21,108 +21,103 @@ * @requires crypto/hash * @requires crypto/public_key/jsbn * @requires crypto/random + * @requires config * @requires util * @module crypto/public_key/dsa */ -import BigInteger from './jsbn.js'; +import BN from 'bn.js'; +import hash from '../hash'; import random from '../random.js'; -import hashModule from '../hash'; -import util from '../../util.js'; import config from '../../config'; +import util from '../../util'; -export default function DSA() { - // s1 = ((g**s) mod p) mod q - // s1 = ((s**-1)*(sha-1(m)+(s1*x) mod q) - function sign(hashalgo, m, g, p, q, x) { +const one = new BN(1); +const zero = new BN(0); + +/* + TODO regarding the hash function, read: + https://tools.ietf.org/html/rfc4880#section-13.6 + https://tools.ietf.org/html/rfc4880#section-14 +*/ + +export default { + /* + * hash_algo is integer + * m is string + * g, p, q, x are all BN + * returns { r: BN, s: BN } + */ + sign: function(hash_algo, m, g, p, q, x) { + let k; + let r; + let s; + let t; + // TODO benchmark BN.mont vs BN.red + const redp = new BN.red(p); + const redq = new BN.red(q); + const gred = g.toRed(redp); + const xred = x.toRed(redq); // If the output size of the chosen hash is larger than the number of // bits of q, the hash result is truncated to fit by taking the number // of leftmost bits equal to the number of bits of q. This (possibly // truncated) hash function result is treated as a number and used // directly in the DSA signature algorithm. - const hashed_data = util.getLeftNBits(util.Uint8Array2str(hashModule.digest(hashalgo, util.str2Uint8Array(m))), q.bitLength()); - const hash = new BigInteger(util.hexstrdump(hashed_data), 16); + // TODO rewrite getLeftNBits to work with Uint8Arrays + const h = new BN( + util.str2Uint8Array( + util.getLeftNBits( + util.Uint8Array2str(hash.digest(hash_algo, m)), q.bitLength()))); // FIPS-186-4, section 4.6: // The values of r and s shall be checked to determine if r = 0 or s = 0. // If either r = 0 or s = 0, a new value of k shall be generated, and the // signature shall be recalculated. It is extremely unlikely that r = 0 // or s = 0 if signatures are generated properly. - let k; - let s1; - let s2; while (true) { - k = random.getRandomBigIntegerInRange(BigInteger.ONE, q.subtract(BigInteger.ONE)); - s1 = (g.modPow(k, p)).mod(q); - s2 = (k.modInverse(q).multiply(hash.add(x.multiply(s1)))).mod(q); - if (s1 !== 0 && s2 !== 0) { - break; + k = random.getRandomBN(one, q); + r = gred.redPow(k).fromRed().toRed(redq); // (g**k mod p) mod q + if (zero.cmp(r) === 0) { + continue; } + t = h.add(x.mul(r)).toRed(redq); // H(m) + x*r mod q + s = k.toRed(redq).redInvm().redMul(t); // k**-1 * (H(m) + x*r) mod q + if (zero.cmp(s) === 0) { + continue; + } + break; } - const result = []; - result[0] = s1.toMPI(); - result[1] = s2.toMPI(); - return result; - } + return {r: r.fromRed(), s: s.fromRed()}; + }, - function select_hash_algorithm(q) { - const usersetting = config.prefer_hash_algorithm; - /* - * 1024-bit key, 160-bit q, SHA-1, SHA-224, SHA-256, SHA-384, or SHA-512 hash - * 2048-bit key, 224-bit q, SHA-224, SHA-256, SHA-384, or SHA-512 hash - * 2048-bit key, 256-bit q, SHA-256, SHA-384, or SHA-512 hash - * 3072-bit key, 256-bit q, SHA-256, SHA-384, or SHA-512 hash - */ - switch (Math.round(q.bitLength() / 8)) { - case 20: - // 1024 bit - if (usersetting !== 2 && - usersetting > 11 && - usersetting !== 10 && - usersetting < 8) { - return 2; // prefer sha1 - } - return usersetting; - case 28: - // 2048 bit - if (usersetting > 11 && - usersetting < 8) { - return 11; - } - return usersetting; - case 32: - // 4096 bit // prefer sha224 - if (usersetting > 10 && - usersetting < 8) { - return 8; // prefer sha256 - } - return usersetting; - default: - util.print_debug("DSA select hash algorithm: returning null for an unknown length of q"); - return null; - } - } - this.select_hash_algorithm = select_hash_algorithm; - - function verify(hashalgo, s1, s2, m, p, q, g, y) { - const hashed_data = util.getLeftNBits(util.Uint8Array2str(hashModule.digest(hashalgo, util.str2Uint8Array(m))), q.bitLength()); - const hash = new BigInteger(util.hexstrdump(hashed_data), 16); - if (BigInteger.ZERO.compareTo(s1) >= 0 || - s1.compareTo(q) >= 0 || - BigInteger.ZERO.compareTo(s2) >= 0 || - s2.compareTo(q) >= 0) { + /* + * hash_algo is integer + * r, s are both BN + * m is string + * p, q, g, y are all BN + * returns BN + */ + verify: function(hash_algo, r, s, m, p, q, g, y) { + if (zero.ucmp(r) >= 0 || r.ucmp(q) >= 0 || + zero.ucmp(s) >= 0 || s.ucmp(q) >= 0) { util.print_debug("invalid DSA Signature"); return null; } - const w = s2.modInverse(q); - if (BigInteger.ZERO.compareTo(w) === 0) { + const redp = new BN.red(p); + const redq = new BN.red(q); + const h = new BN( + util.str2Uint8Array( + util.getLeftNBits( + util.Uint8Array2str(hash.digest(hash_algo, m)), q.bitLength()))); + const w = s.toRed(redq).redInvm(); // s**-1 mod q + if (zero.cmp(w) === 0) { util.print_debug("invalid DSA Signature"); return null; } - const u1 = hash.multiply(w).mod(q); - const u2 = s1.multiply(w).mod(q); - return g.modPow(u1, p).multiply(y.modPow(u2, p)).mod(p).mod(q); + const u1 = h.toRed(redq).redMul(w); // H(m) * w mod q + const u2 = r.toRed(redq).redMul(w); // r * w mod q + const t1 = g.toRed(redp).redPow(u1.fromRed()); // g**u1 mod p + const t2 = y.toRed(redp).redPow(u2.fromRed()); // y**u2 mod p + const v = t1.redMul(t2).fromRed().mod(q); // (g**u1 * y**u2 mod p) mod q + return v.cmp(r) === 0; } - - this.sign = sign; - this.verify = verify; -} +}; diff --git a/src/crypto/random.js b/src/crypto/random.js index 0c8f43c2..a36b145c 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -18,13 +18,15 @@ // The GPG4Browsers crypto interface /** + * @requires bn.js * @requires type/mpi * @requires util * @module crypto/random */ -import type_mpi from '../type/mpi.js'; -import util from '../util.js'; +import BN from 'bn.js'; +import type_mpi from '../type/mpi'; +import util from '../util'; // Do not use util.getNodeCrypto because we need this regardless of use_native setting const nodeCrypto = util.detectNode() && require('crypto'); @@ -107,9 +109,9 @@ export default { let randomBits = util.Uint8Array2str(this.getRandomBytes(numBytes)); if (bits % 8 > 0) { - randomBits = String.fromCharCode(((2 ** (bits % 8)) - 1) & - randomBits.charCodeAt(0)) + - randomBits.substring(1); + randomBits = String.fromCharCode( + ((2 ** (bits % 8)) - 1) & randomBits.charCodeAt(0) + ) + randomBits.substring(1); } const mpi = new type_mpi(randomBits); return mpi.toBigInteger(); @@ -128,6 +130,27 @@ export default { return min.add(r); }, + /** + * Create a secure random MPI in specified range + * @param {module:type/mpi} min Lower bound, included + * @param {module:type/mpi} max Upper bound, excluded + * @return {module:BN} Random MPI + */ + getRandomBN: function(min, max) { + if (max.cmp(min) <= 0) { + throw new Error('Illegal parameter value: max <= min'); + } + + const modulus = max.sub(min); + const length = modulus.byteLength(); + let r = new BN(this.getRandomBytes(length)); + // Using a while loop is necessary to avoid bias + while (r.cmp(modulus) >= 0) { + r = new BN(this.getRandomBytes(length)); + } + return r.iadd(min); + }, + randomBuffer: new RandomBuffer() }; diff --git a/src/crypto/signature.js b/src/crypto/signature.js index cc5b6b7c..0bbbbbd6 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -27,6 +27,7 @@ export default { let s; let Q; let curve; + let signature; data = util.Uint8Array2str(data); @@ -51,16 +52,15 @@ export default { } case 17: { // DSA (Digital Signature Algorithm) [FIPS186] [HAC] - const dsa = new publicKey.dsa(); - const s1 = msg_MPIs[0].toBigInteger(); - const s2 = msg_MPIs[1].toBigInteger(); - const p = publickey_MPIs[0].toBigInteger(); - const q = publickey_MPIs[1].toBigInteger(); - const g = publickey_MPIs[2].toBigInteger(); - const y = publickey_MPIs[3].toBigInteger(); - m = data; - const dopublic = dsa.verify(hash_algo, s1, s2, m, p, q, g, y); - return dopublic.compareTo(s1) === 0; + const dsa = publicKey.dsa; + r = msg_MPIs[0].toBN(); + s = msg_MPIs[1].toBN(); + const p = publickey_MPIs[0].toBN(); + const q = publickey_MPIs[1].toBN(); + const g = publickey_MPIs[2].toBN(); + const y = publickey_MPIs[3].toBN(); + m = util.str2Uint8Array(data); + return dsa.verify(hash_algo, r, s, m, p, q, g, y); } case 19: { // ECDSA @@ -122,15 +122,17 @@ export default { } case 17: { // DSA (Digital Signature Algorithm) [FIPS186] [HAC] - const dsa = new publicKey.dsa(); - - const p = keyIntegers[0].toBigInteger(); - const q = keyIntegers[1].toBigInteger(); - const g = keyIntegers[2].toBigInteger(); - const x = keyIntegers[4].toBigInteger(); - m = data; - const result = dsa.sign(hash_algo, m, g, p, q, x); - return util.str2Uint8Array(result[0].toString() + result[1].toString()); + const dsa = publicKey.dsa; + const p = keyIntegers[0].toBN(); + const q = keyIntegers[1].toBN(); + const g = keyIntegers[2].toBN(); + const x = keyIntegers[4].toBN(); + m = util.str2Uint8Array(data); + signature = dsa.sign(hash_algo, m, g, p, q, x); + return util.concatUint8Array([ + util.Uint8Array2MPI(signature.r.toArrayLike(Uint8Array)), + util.Uint8Array2MPI(signature.s.toArrayLike(Uint8Array)) + ]); } case 16: { // Elgamal (Encrypt-Only) [ELGAMAL] [HAC] diff --git a/src/type/mpi.js b/src/type/mpi.js index 08d7973d..466f6a5f 100644 --- a/src/type/mpi.js +++ b/src/type/mpi.js @@ -29,11 +29,13 @@ * An MPI consists of two pieces: a two-octet scalar that is the length * of the MPI in bits followed by a string of octets that contain the * actual integer. + * @requires bn.js * @requires crypto/public_key/jsbn * @requires util * @module type/mpi */ +import BN from 'bn.js'; import BigInteger from '../crypto/public_key/jsbn'; import util from '../util'; @@ -110,6 +112,10 @@ MPI.prototype.toBigInteger = function () { return this.data.clone(); }; +MPI.prototype.toBN = function (bn) { + return new BN(this.write().slice(2)); +}; + MPI.prototype.fromBigInteger = function (bn) { this.data = bn.clone(); }; diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 5be914fe..9979d5a9 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -1,5 +1,5 @@ const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const asmCrypto = require('asmcrypto-lite'); +const asmCrypto = require('asmcrypto.js'); const chai = require('chai'); chai.use(require('chai-as-promised'));