Drop BigInteger class, use standalone helpers

This commit is contained in:
larabr 2024-05-10 13:06:09 +02:00 committed by larabr
parent 90495522f7
commit cf0285add5
16 changed files with 572 additions and 739 deletions

13
package-lock.json generated
View File

@ -27,6 +27,7 @@
"@typescript-eslint/parser": "^7.8.0",
"argon2id": "^1.0.1",
"benchmark": "^2.1.4",
"bn.js": "^5.2.1",
"c8": "^8.0.1",
"chai": "^4.4.1",
"chai-as-promised": "^7.1.1",
@ -2141,6 +2142,12 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
"node_modules/bn.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
"dev": true
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
@ -9783,6 +9790,12 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
"bn.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
"dev": true
},
"body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",

View File

@ -80,6 +80,7 @@
"@typescript-eslint/parser": "^7.8.0",
"argon2id": "^1.0.1",
"benchmark": "^2.1.4",
"bn.js": "^5.2.1",
"c8": "^8.0.1",
"chai": "^4.4.1",
"chai-as-promised": "^7.1.1",

View File

@ -64,7 +64,9 @@ export default Object.assign([
resolve({
browser: true
}),
typescript(),
typescript({
compilerOptions: { outDir: './dist/tmp-ts' } // to avoid js files being overwritten
}),
commonjs({
ignore: nodeBuiltinModules.concat(nodeDependencies)
}),
@ -89,7 +91,9 @@ export default Object.assign([
resolve({
exportConditions: ['node'] // needed for resolution of noble-curves import of '@noble/crypto' in Node 16 and 18
}),
typescript(),
typescript({
compilerOptions: { outDir: './dist/tmp-ts' }
}),
commonjs(),
replace({
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`
@ -109,7 +113,9 @@ export default Object.assign([
resolve({
browser: true
}),
typescript(),
typescript({
compilerOptions: { outDir: './dist/lightweight/tmp-ts' }
}),
commonjs({
ignore: nodeBuiltinModules.concat(nodeDependencies)
}),
@ -136,7 +142,9 @@ export default Object.assign([
resolve({
browser: true
}),
typescript(),
typescript({
compilerOptions: { outDir: './test/lib/tmp-ts' }
}),
commonjs({
ignore: nodeBuiltinModules.concat(nodeDependencies),
requireReturnsDefault: 'preferred'

View File

@ -1,499 +0,0 @@
/**
* @fileoverview
* BigInteger implementation of basic operations
* that wraps the native BigInt library.
* Operations are not constant time,
* but we try and limit timing leakage where we can
*/
export default class BigInteger {
private value: bigint;
/**
* Get a BigInteger (input must be big endian for strings and arrays)
* @param {Number|String|Uint8Array} n - Value to convert
* @throws {Error} on null or undefined input
*/
constructor(n: Uint8Array | string | number | bigint) {
if (n === undefined) {
throw new Error('Invalid BigInteger input');
}
if (n instanceof Uint8Array) {
const bytes = n;
const hexAlphabet = '0123456789ABCDEF';
let s = '';
bytes.forEach(v => {
s += hexAlphabet[v >> 4] + hexAlphabet[v & 15];
});
this.value = BigInt('0x0' + s);
} else {
this.value = BigInt(n);
}
}
clone() {
return new BigInteger(this.value);
}
/**
* BigInteger increment in place
*/
iinc() {
this.value++;
return this;
}
/**
* BigInteger increment
* @returns {BigInteger} this + 1.
*/
inc() {
return this.clone().iinc();
}
/**
* BigInteger decrement in place
*/
idec() {
this.value--;
return this;
}
/**
* BigInteger decrement
* @returns {BigInteger} this - 1.
*/
dec() {
return this.clone().idec();
}
/**
* BigInteger addition in place
* @param {BigInteger} x - Value to add
*/
iadd(x: BigInteger) {
this.value += x.value;
return this;
}
/**
* BigInteger addition
* @param {BigInteger} x - Value to add
* @returns {BigInteger} this + x.
*/
add(x: BigInteger) {
return this.clone().iadd(x);
}
/**
* BigInteger subtraction in place
* @param {BigInteger} x - Value to subtract
*/
isub(x: BigInteger) {
this.value -= x.value;
return this;
}
/**
* BigInteger subtraction
* @param {BigInteger} x - Value to subtract
* @returns {BigInteger} this - x.
*/
sub(x: BigInteger) {
return this.clone().isub(x);
}
/**
* BigInteger multiplication in place
* @param {BigInteger} x - Value to multiply
*/
imul(x: BigInteger) {
this.value *= x.value;
return this;
}
/**
* BigInteger multiplication
* @param {BigInteger} x - Value to multiply
* @returns {BigInteger} this * x.
*/
mul(x: BigInteger) {
return this.clone().imul(x);
}
/**
* Compute value modulo m, in place
* @param {BigInteger} m - Modulo
*/
imod(m: BigInteger) {
this.value %= m.value;
if (this.isNegative()) {
this.iadd(m);
}
return this;
}
/**
* Compute value modulo m
* @param {BigInteger} m - Modulo
* @returns {BigInteger} this mod m.
*/
mod(m: BigInteger) {
return this.clone().imod(m);
}
/**
* Compute modular exponentiation using square and multiply
* @param {BigInteger} e - Exponent
* @param {BigInteger} n - Modulo
* @returns {BigInteger} this ** e mod n.
*/
modExp(e: BigInteger, n: BigInteger) {
if (n.isZero()) throw Error('Modulo cannot be zero');
if (n.isOne()) return new BigInteger(0);
if (e.isNegative()) throw Error('Unsopported negative exponent');
let exp = e.value;
let x = this.value;
x %= n.value;
let r = BigInt(1);
while (exp > BigInt(0)) {
const lsb = exp & BigInt(1);
exp >>= BigInt(1); // e / 2
// Always compute multiplication step, to reduce timing leakage
const rx = (r * x) % n.value;
// Update r only if lsb is 1 (odd exponent)
r = lsb ? rx : r;
x = (x * x) % n.value; // Square
}
return new BigInteger(r);
}
/**
* Compute the inverse of this value modulo n
* Note: this and and n must be relatively prime
* @param {BigInteger} n - Modulo
* @returns {BigInteger} x such that this*x = 1 mod n
* @throws {Error} if the inverse does not exist
*/
modInv(n: BigInteger) {
const { gcd, x } = this._egcd(n);
if (!gcd.isOne()) {
throw new Error('Inverse does not exist');
}
return x.add(n).mod(n);
}
/**
* BigInteger division, in place
* @param {BigInteger} n - Value to divide
*/
idiv(n: BigInteger) {
this.value /= n.value;
return this;
}
/**
* BigInteger division
* @param {BigInteger} n - Value to divide
* @returns {BigInteger} this divded by n.
*/
div(n: BigInteger) {
return this.clone().idiv(n);
}
/**
* Extended Eucleadian algorithm (http://anh.cs.luc.edu/331/notes/xgcd.pdf)
* Given a = this and b, compute (x, y) such that ax + by = gdc(a, b).
* Negative numbers are also supported.
* @param {BigInteger} b - Second operand
* @returns {{ gcd, x, y: BigInteger }}
*/
private _egcd(bInput: BigInteger) {
let x = BigInt(0);
let y = BigInt(1);
let xPrev = BigInt(1);
let yPrev = BigInt(0);
// Deal with negative numbers: run algo over absolute values,
// and "move" the sign to the returned x and/or y.
// See https://math.stackexchange.com/questions/37806/extended-euclidean-algorithm-with-negative-numbers
let a = this.abs().value;
let b = bInput.abs().value;
const aNegated = this.isNegative();
const bNegated = bInput.isNegative();
while (b !== BigInt(0)) {
const q = a / b;
let tmp = x;
x = xPrev - q * x;
xPrev = tmp;
tmp = y;
y = yPrev - q * y;
yPrev = tmp;
tmp = b;
b = a % b;
a = tmp;
}
return {
x: new BigInteger(aNegated ? -xPrev : xPrev),
y: new BigInteger(bNegated ? -yPrev : yPrev),
gcd: new BigInteger(a)
};
}
/**
* Compute greatest common divisor between this and n
* @param {BigInteger} b - Operand
* @returns {BigInteger} gcd
*/
gcd(bInput: BigInteger) {
let a = this.value;
let b = bInput.value;
while (b !== BigInt(0)) {
const tmp = b;
b = a % b;
a = tmp;
}
return new BigInteger(a);
}
/**
* Shift this to the left by x, in place
* @param {BigInteger} x - Shift value
*/
ileftShift(x: BigInteger) {
this.value <<= x.value;
return this;
}
/**
* Shift this to the left by x
* @param {BigInteger} x - Shift value
* @returns {BigInteger} this << x.
*/
leftShift(x: BigInteger) {
return this.clone().ileftShift(x);
}
/**
* Shift this to the right by x, in place
* @param {BigInteger} x - Shift value
*/
irightShift(x: BigInteger) {
this.value >>= x.value;
return this;
}
/**
* Shift this to the right by x
* @param {BigInteger} x - Shift value
* @returns {BigInteger} this >> x.
*/
rightShift(x: BigInteger) {
return this.clone().irightShift(x);
}
ixor(x: BigInteger) {
this.value ^= x.value;
return this;
}
xor(x: BigInteger) {
return this.clone().ixor(x);
}
ibitwiseAnd(x: BigInteger) {
this.value &= x.value;
return this;
}
bitwiseAnd(x: BigInteger) {
return this.clone().ibitwiseAnd(x);
}
ibitwiseOr(x: BigInteger) {
this.value |= x.value;
return this;
}
/**
* Whether this value is equal to x
* @param {BigInteger} x
* @returns {Boolean}
*/
equal(x: BigInteger) {
return this.value === x.value;
}
/**
* Whether this value is less than x
* @param {BigInteger} x
* @returns {Boolean}
*/
lt(x: BigInteger) {
return this.value < x.value;
}
/**
* Whether this value is less than or equal to x
* @param {BigInteger} x
* @returns {Boolean}
*/
lte(x: BigInteger) {
return this.value <= x.value;
}
/**
* Whether this value is greater than x
* @param {BigInteger} x
* @returns {Boolean}
*/
gt(x: BigInteger) {
return this.value > x.value;
}
/**
* Whether this value is greater than or equal to x
* @param {BigInteger} x
* @returns {Boolean}
*/
gte(x: BigInteger) {
return this.value >= x.value;
}
isZero() {
return this.value === BigInt(0);
}
isOne() {
return this.value === BigInt(1);
}
isNegative() {
return this.value < BigInt(0);
}
isEven() {
return !(this.value & BigInt(1));
}
abs() {
const res = this.clone();
if (this.isNegative()) {
res.value = -res.value;
}
return res;
}
negate() {
const res = this.clone();
res.value = -res.value;
return res;
}
/**
* Get this value as a string
* @returns {String} this value.
*/
toString() {
return this.value.toString();
}
/**
* Get this value as an exact Number (max 53 bits)
* Fails if this value is too large
* @returns {Number}
*/
toNumber() {
const number = Number(this.value);
if (number > Number.MAX_SAFE_INTEGER) {
// We throw and error to conform with the bn.js implementation
throw new Error('Number can only safely store up to 53 bits');
}
return number;
}
/**
* Get value of i-th bit
* @param {Number} i - Bit index
* @returns {Number} Bit value.
*/
getBit(i: number) {
const bit = (this.value >> BigInt(i)) & BigInt(1);
return bit === BigInt(0) ? 0 : 1;
}
/**
* Compute bit length
* @returns {Number} Bit length.
*/
bitLength() {
const zero = new BigInteger(0);
const one = new BigInteger(1);
const negOne = new BigInteger(-1);
// -1n >> -1n is -1n
// 1n >> 1n is 0n
const target = this.isNegative() ? negOne : zero;
let bitlen = 1;
const tmp = this.clone();
while (!tmp.irightShift(one).equal(target)) {
bitlen++;
}
return bitlen;
}
/**
* Compute byte length
* @returns {Number} Byte length.
*/
byteLength() {
const zero = new BigInteger(0);
const negOne = new BigInteger(-1);
const target = this.isNegative() ? negOne : zero;
const eight = new BigInteger(8);
let len = 1;
const tmp = this.clone();
while (!tmp.irightShift(eight).equal(target)) {
len++;
}
return len;
}
/**
* Get Uint8Array representation of this number
* @param {String} endian - Endianess of output array (defaults to 'be')
* @param {Number} length - Of output array
* @returns {Uint8Array}
*/
toUint8Array(endian = 'be', length: number) {
// we get and parse the hex string (https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/)
// this is faster than shift+mod iterations
let hex = this.value.toString(16);
if (hex.length % 2 === 1) {
hex = '0' + hex;
}
const rawLength = hex.length / 2;
const bytes = new Uint8Array(length || rawLength);
// parse hex
const offset = length ? length - rawLength : 0;
let i = 0;
while (i < rawLength) {
bytes[i + offset] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
i++;
}
if (endian !== 'be') {
bytes.reverse();
}
return bytes;
}
}

216
src/crypto/biginteger.ts Normal file
View File

@ -0,0 +1,216 @@
// Operations are not constant time, but we try and limit timing leakage where we can
const _0n = BigInt(0);
const _1n = BigInt(1);
export function uint8ArrayToBigInt(bytes: Uint8Array) {
const hexAlphabet = '0123456789ABCDEF';
let s = '';
bytes.forEach(v => {
s += hexAlphabet[v >> 4] + hexAlphabet[v & 15];
});
return BigInt('0x0' + s);
}
export function mod(a: bigint, m: bigint) {
const reduced = a % m;
return reduced < _0n ? reduced + m : reduced;
}
/**
* Compute modular exponentiation using square and multiply
* @param {BigInt} a - Base
* @param {BigInt} e - Exponent
* @param {BigInt} n - Modulo
* @returns {BigInt} b ** e mod n.
*/
export function modExp(b: bigint, e: bigint, n: bigint) {
if (n === _0n) throw Error('Modulo cannot be zero');
if (n === _1n) return BigInt(0);
if (e < _0n) throw Error('Unsopported negative exponent');
let exp = e;
let x = b;
x %= n;
let r = BigInt(1);
while (exp > _0n) {
const lsb = exp & _1n;
exp >>= _1n; // e / 2
// Always compute multiplication step, to reduce timing leakage
const rx = (r * x) % n;
// Update r only if lsb is 1 (odd exponent)
r = lsb ? rx : r;
x = (x * x) % n; // Square
}
return r;
}
function abs(x: bigint) {
return x >= _0n ? x : -x;
}
/**
* Extended Eucleadian algorithm (http://anh.cs.luc.edu/331/notes/xgcd.pdf)
* Given a and b, compute (x, y) such that ax + by = gdc(a, b).
* Negative numbers are also supported.
* @param {BigInt} a - First operand
* @param {BigInt} b - Second operand
* @returns {{ gcd, x, y: bigint }}
*/
function _egcd(aInput: bigint, bInput: bigint) {
let x = BigInt(0);
let y = BigInt(1);
let xPrev = BigInt(1);
let yPrev = BigInt(0);
// Deal with negative numbers: run algo over absolute values,
// and "move" the sign to the returned x and/or y.
// See https://math.stackexchange.com/questions/37806/extended-euclidean-algorithm-with-negative-numbers
let a = abs(aInput);
let b = abs(bInput);
const aNegated = aInput < _0n;
const bNegated = bInput < _0n;
while (b !== _0n) {
const q = a / b;
let tmp = x;
x = xPrev - q * x;
xPrev = tmp;
tmp = y;
y = yPrev - q * y;
yPrev = tmp;
tmp = b;
b = a % b;
a = tmp;
}
return {
x: aNegated ? -xPrev : xPrev,
y: bNegated ? -yPrev : yPrev,
gcd: a
};
}
/**
* Compute the inverse of `a` modulo `n`
* Note: `a` and and `n` must be relatively prime
* @param {BigInt} a
* @param {BigInt} n - Modulo
* @returns {BigInt} x such that a*x = 1 mod n
* @throws {Error} if the inverse does not exist
*/
export function modInv(a: bigint, n: bigint) {
const { gcd, x } = _egcd(a, n);
if (gcd !== _1n) {
throw new Error('Inverse does not exist');
}
return mod(x + n, n);
}
/**
* Compute greatest common divisor between this and n
* @param {BigInt} aInput - Operand
* @param {BigInt} bInput - Operand
* @returns {BigInt} gcd
*/
export function gcd(aInput: bigint, bInput: bigint) {
let a = aInput;
let b = bInput;
while (b !== _0n) {
const tmp = b;
b = a % b;
a = tmp;
}
return a;
}
/**
* Get this value as an exact Number (max 53 bits)
* Fails if this value is too large
* @returns {Number}
*/
export function bigIntToNumber(x: bigint) {
const number = Number(x);
if (number > Number.MAX_SAFE_INTEGER) {
// We throw and error to conform with the bn.js implementation
throw new Error('Number can only safely store up to 53 bits');
}
return number;
}
/**
* Get value of i-th bit
* @param {BigInt} x
* @param {Number} i - Bit index
* @returns {Number} Bit value.
*/
export function getBit(x:bigint, i: number) {
const bit = (x >> BigInt(i)) & _1n;
return bit === _0n ? 0 : 1;
}
/**
* Compute bit length
*/
export function bitLength(x: bigint) {
// -1n >> -1n is -1n
// 1n >> 1n is 0n
const target = x < _0n ? BigInt(-1) : _0n;
let bitlen = 1;
let tmp = x;
// eslint-disable-next-line no-cond-assign
while ((tmp >>= _1n) !== target) {
bitlen++;
}
return bitlen;
}
/**
* Compute byte length
*/
export function byteLength(x: bigint) {
const target = x < _0n ? BigInt(-1) : _0n;
const _8n = BigInt(8);
let len = 1;
let tmp = x;
// eslint-disable-next-line no-cond-assign
while ((tmp >>= _8n) !== target) {
len++;
}
return len;
}
/**
* Get Uint8Array representation of this number
* @param {String} endian - Endianess of output array (defaults to 'be')
* @param {Number} length - Of output array
* @returns {Uint8Array}
*/
export function bigIntToUint8Array(x: bigint, endian = 'be', length: number) {
// we get and parse the hex string (https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/)
// this is faster than shift+mod iterations
let hex = x.toString(16);
if (hex.length % 2 === 1) {
hex = '0' + hex;
}
const rawLength = hex.length / 2;
const bytes = new Uint8Array(length || rawLength);
// parse hex
const offset = length ? length - rawLength : 0;
let i = 0;
while (i < rawLength) {
bytes[i + offset] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
i++;
}
if (endian !== 'be') {
bytes.reverse();
}
return bytes;
}

View File

@ -132,7 +132,7 @@ export function emeDecode(encoded, randomPayload) {
* @param {Integer} emLen - Intended length in octets of the encoded message
* @returns {Uint8Array} Encoded message.
*/
export async function emsaEncode(algo, hashed, emLen) {
export function emsaEncode(algo, hashed, emLen) {
let i;
if (hashed.length !== hash.getHashByteLength(algo)) {
throw new Error('Invalid hash length');

View File

@ -22,7 +22,7 @@
import { getRandomBigInteger } from '../random';
import util from '../../util';
import { isProbablePrime } from './prime';
import BigInteger from '../../biginteger';
import { bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
/*
TODO regarding the hash function, read:
@ -30,6 +30,9 @@ import BigInteger from '../../biginteger';
https://tools.ietf.org/html/rfc4880#section-14
*/
const _0n = BigInt(0);
const _1n = BigInt(1);
/**
* DSA Sign function
* @param {Integer} hashAlgo
@ -42,24 +45,24 @@ import BigInteger from '../../biginteger';
* @async
*/
export async function sign(hashAlgo, hashed, g, p, q, x) {
const one = new BigInteger(1);
p = new BigInteger(p);
q = new BigInteger(q);
g = new BigInteger(g);
x = new BigInteger(x);
const _0n = BigInt(0);
p = uint8ArrayToBigInt(p);
q = uint8ArrayToBigInt(q);
g = uint8ArrayToBigInt(g);
x = uint8ArrayToBigInt(x);
let k;
let r;
let s;
let t;
g = g.mod(p);
x = x.mod(q);
g = mod(g, p);
x = mod(x, q);
// 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 h = new BigInteger(hashed.subarray(0, q.byteLength())).mod(q);
const h = mod(uint8ArrayToBigInt(hashed.subarray(0, byteLength(q))), q);
// 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
@ -67,22 +70,22 @@ export async function sign(hashAlgo, hashed, g, p, q, x) {
// or s = 0 if signatures are generated properly.
while (true) {
// See Appendix B here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
k = await getRandomBigInteger(one, q); // returns in [1, q-1]
r = g.modExp(k, p).imod(q); // (g**k mod p) mod q
if (r.isZero()) {
k = getRandomBigInteger(_1n, q); // returns in [1, q-1]
r = mod(modExp(g, k, p), q); // (g**k mod p) mod q
if (r === _0n) {
continue;
}
const xr = x.mul(r).imod(q);
t = h.add(xr).imod(q); // H(m) + x*r mod q
s = k.modInv(q).imul(t).imod(q); // k**-1 * (H(m) + x*r) mod q
if (s.isZero()) {
const xr = mod(x * r, q);
t = mod(h + xr, q); // H(m) + x*r mod q
s = mod(modInv(k, q) * t, q); // k**-1 * (H(m) + x*r) mod q
if (s === _0n) {
continue;
}
break;
}
return {
r: r.toUint8Array('be', q.byteLength()),
s: s.toUint8Array('be', q.byteLength())
r: bigIntToUint8Array(r, 'be', byteLength(p)),
s: bigIntToUint8Array(s, 'be', byteLength(p))
};
}
@ -100,35 +103,34 @@ export async function sign(hashAlgo, hashed, g, p, q, x) {
* @async
*/
export async function verify(hashAlgo, r, s, hashed, g, p, q, y) {
const zero = new BigInteger(0);
r = new BigInteger(r);
s = new BigInteger(s);
r = uint8ArrayToBigInt(r);
s = uint8ArrayToBigInt(s);
p = new BigInteger(p);
q = new BigInteger(q);
g = new BigInteger(g);
y = new BigInteger(y);
p = uint8ArrayToBigInt(p);
q = uint8ArrayToBigInt(q);
g = uint8ArrayToBigInt(g);
y = uint8ArrayToBigInt(y);
if (r.lte(zero) || r.gte(q) ||
s.lte(zero) || s.gte(q)) {
if (r <= _0n || r >= q ||
s <= _0n || s >= q) {
util.printDebug('invalid DSA Signature');
return false;
}
const h = new BigInteger(hashed.subarray(0, q.byteLength())).imod(q);
const w = s.modInv(q); // s**-1 mod q
if (w.isZero()) {
const h = mod(uint8ArrayToBigInt(hashed.subarray(0, byteLength(q))), q);
const w = modInv(s, q); // s**-1 mod q
if (w === _0n) {
util.printDebug('invalid DSA Signature');
return false;
}
g = g.mod(p);
y = y.mod(p);
const u1 = h.mul(w).imod(q); // H(m) * w mod q
const u2 = r.mul(w).imod(q); // r * w mod q
const t1 = g.modExp(u1, p); // g**u1 mod p
const t2 = y.modExp(u2, p); // y**u2 mod p
const v = t1.mul(t2).imod(p).imod(q); // (g**u1 * y**u2 mod p) mod q
return v.equal(r);
g = mod(g, p);
y = mod(y, p);
const u1 = mod(h * w, q); // H(m) * w mod q
const u2 = mod(r * w, q); // r * w mod q
const t1 = modExp(g, u1, p); // g**u1 mod p
const t2 = modExp(y, u2, p); // y**u2 mod p
const v = mod(mod(t1 * t2, p), q); // (g**u1 * y**u2 mod p) mod q
return v === r;
}
/**
@ -142,20 +144,19 @@ export async function verify(hashAlgo, r, s, hashed, g, p, q, y) {
* @async
*/
export async function validateParams(p, q, g, y, x) {
p = new BigInteger(p);
q = new BigInteger(q);
g = new BigInteger(g);
y = new BigInteger(y);
const one = new BigInteger(1);
p = uint8ArrayToBigInt(p);
q = uint8ArrayToBigInt(q);
g = uint8ArrayToBigInt(g);
y = uint8ArrayToBigInt(y);
// Check that 1 < g < p
if (g.lte(one) || g.gte(p)) {
if (g <= _1n || g >= p) {
return false;
}
/**
* Check that subgroup order q divides p-1
*/
if (!p.dec().mod(q).isZero()) {
if (mod(p - _1n, q) !== _0n) {
return false;
}
@ -163,16 +164,16 @@ export async function validateParams(p, q, g, y, x) {
* g has order q
* Check that g ** q = 1 mod p
*/
if (!g.modExp(q, p).isOne()) {
if (modExp(g, q, p) !== _1n) {
return false;
}
/**
* Check q is large and probably prime (we mainly want to avoid small factors)
*/
const qSize = new BigInteger(q.bitLength());
const n150 = new BigInteger(150);
if (qSize.lt(n150) || !(await isProbablePrime(q, null, 32))) {
const qSize = BigInt(bitLength(q));
const _150n = BigInt(150);
if (qSize < _150n || !isProbablePrime(q, null, 32)) {
return false;
}
@ -182,11 +183,11 @@ export async function validateParams(p, q, g, y, x) {
*
* Blinded exponentiation computes g**{rq + x} to compare to y
*/
x = new BigInteger(x);
const two = new BigInteger(2);
const r = await getRandomBigInteger(two.leftShift(qSize.dec()), two.leftShift(qSize)); // draw r of same size as q
const rqx = q.mul(r).add(x);
if (!y.equal(g.modExp(rqx, p))) {
x = uint8ArrayToBigInt(x);
const _2n = BigInt(2);
const r = getRandomBigInteger(_2n << (qSize - _1n), _2n << qSize); // draw r of same size as q
const rqx = q * r + x;
if (y !== modExp(g, rqx, p)) {
return false;
}

View File

@ -21,7 +21,9 @@
*/
import { getRandomBigInteger } from '../random';
import { emeEncode, emeDecode } from '../pkcs1';
import BigInteger from '../../biginteger';
import { bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
const _1n = BigInt(1);
/**
* ElGamal Encryption function
@ -34,19 +36,19 @@ import BigInteger from '../../biginteger';
* @async
*/
export async function encrypt(data, p, g, y) {
p = new BigInteger(p);
g = new BigInteger(g);
y = new BigInteger(y);
p = uint8ArrayToBigInt(p);
g = uint8ArrayToBigInt(g);
y = uint8ArrayToBigInt(y);
const padded = emeEncode(data, p.byteLength());
const m = new BigInteger(padded);
const padded = emeEncode(data, byteLength(p));
const m = uint8ArrayToBigInt(padded);
// OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ*
// hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2]
const k = await getRandomBigInteger(new BigInteger(1), p.dec());
const k = getRandomBigInteger(_1n, p - _1n);
return {
c1: g.modExp(k, p).toUint8Array(),
c2: y.modExp(k, p).imul(m).imod(p).toUint8Array()
c1: bigIntToUint8Array(modExp(g, k, p)),
c2: bigIntToUint8Array(mod(modExp(y, k, p) * m, p))
};
}
@ -63,13 +65,13 @@ export async function encrypt(data, p, g, y) {
* @async
*/
export async function decrypt(c1, c2, p, x, randomPayload) {
c1 = new BigInteger(c1);
c2 = new BigInteger(c2);
p = new BigInteger(p);
x = new BigInteger(x);
c1 = uint8ArrayToBigInt(c1);
c2 = uint8ArrayToBigInt(c2);
p = uint8ArrayToBigInt(p);
x = uint8ArrayToBigInt(x);
const padded = c1.modExp(x, p).modInv(p).imul(c2).imod(p);
return emeDecode(padded.toUint8Array('be', p.byteLength()), randomPayload);
const padded = mod(modInv(modExp(c1, x, p), p) * c2, p);
return emeDecode(bigIntToUint8Array(padded, 'be', byteLength(p)), randomPayload);
}
/**
@ -82,20 +84,19 @@ export async function decrypt(c1, c2, p, x, randomPayload) {
* @async
*/
export async function validateParams(p, g, y, x) {
p = new BigInteger(p);
g = new BigInteger(g);
y = new BigInteger(y);
p = uint8ArrayToBigInt(p);
g = uint8ArrayToBigInt(g);
y = uint8ArrayToBigInt(y);
const one = new BigInteger(1);
// Check that 1 < g < p
if (g.lte(one) || g.gte(p)) {
if (g <= _1n || g >= p) {
return false;
}
// Expect p-1 to be large
const pSize = new BigInteger(p.bitLength());
const n1023 = new BigInteger(1023);
if (pSize.lt(n1023)) {
const pSize = BigInt(bitLength(p));
const _1023n = BigInt(1023);
if (pSize < _1023n) {
return false;
}
@ -103,7 +104,7 @@ export async function validateParams(p, g, y, x) {
* g should have order p-1
* Check that g ** (p-1) = 1 mod p
*/
if (!g.modExp(p.dec(), p).isOne()) {
if (modExp(g, p - _1n, p) !== _1n) {
return false;
}
@ -114,14 +115,15 @@ export async function validateParams(p, g, y, x) {
* We just check g**i != 1 for all i up to a threshold
*/
let res = g;
const i = new BigInteger(1);
const threshold = new BigInteger(2).leftShift(new BigInteger(17)); // we want order > threshold
while (i.lt(threshold)) {
res = res.mul(g).imod(p);
if (res.isOne()) {
let i = BigInt(1);
const _2n = BigInt(2);
const threshold = _2n << BigInt(17); // we want order > threshold
while (i < threshold) {
res = mod(res * g, p);
if (res === _1n) {
return false;
}
i.iinc();
i++;
}
/**
@ -130,11 +132,10 @@ export async function validateParams(p, g, y, x) {
*
* Blinded exponentiation computes g**{r(p-1) + x} to compare to y
*/
x = new BigInteger(x);
const two = new BigInteger(2);
const r = await getRandomBigInteger(two.leftShift(pSize.dec()), two.leftShift(pSize)); // draw r of same size as p-1
const rqx = p.dec().imul(r).iadd(x);
if (!y.equal(g.modExp(rqx, p))) {
x = uint8ArrayToBigInt(x);
const r = getRandomBigInteger(_2n << (pSize - _1n), _2n << pSize); // draw r of same size as p-1
const rqx = (p - _1n) * r + x;
if (y !== modExp(g, rqx, p)) {
return false;
}

View File

@ -25,7 +25,7 @@ import util from '../../../util';
import { getRandomBytes } from '../../random';
import hash from '../../hash';
import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams, nodeCurves } from './oid_curves';
import BigInteger from '../../../biginteger';
import { bigIntToUint8Array } from '../../biginteger';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
@ -73,8 +73,8 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
// lowS: non-canonical sig: https://stackoverflow.com/questions/74338846/ecdsa-signature-verification-mismatch
const signature = nobleCurve.sign(hashed, privateKey, { lowS: false });
return {
r: new BigInteger(signature.r).toUint8Array('be', curve.payloadSize),
s: new BigInteger(signature.s).toUint8Array('be', curve.payloadSize)
r: bigIntToUint8Array(signature.r, 'be', curve.payloadSize),
s: bigIntToUint8Array(signature.s, 'be', curve.payloadSize)
};
}

View File

@ -19,21 +19,20 @@
* @fileoverview Algorithms for probabilistic random prime generation
* @module crypto/public_key/prime
*/
import BigInteger from '../../biginteger';
import { bigIntToNumber, bitLength, gcd, getBit, mod, modExp } from '../biginteger';
import { getRandomBigInteger } from '../random';
const _1n = BigInt(1);
/**
* Generate a probably prime random number
* @param {Integer} bits - Bit length of the prime
* @param {BigInteger} e - Optional RSA exponent to check against the prime
* @param {Integer} k - Optional number of iterations of Miller-Rabin test
* @returns BigInteger
* @async
* @param bits - Bit length of the prime
* @param e - Optional RSA exponent to check against the prime
* @param k - Optional number of iterations of Miller-Rabin test
*/
export async function randomProbablePrime(bits, e, k) {
const one = new BigInteger(1);
const min = one.leftShift(new BigInteger(bits - 1));
const thirty = new BigInteger(30);
export function randomProbablePrime(bits: number, e: bigint, k: number) {
const _30n = BigInt(30);
const min = _1n << BigInt(bits - 1);
/*
* We can avoid any multiples of 3 and 5 by looking at n mod 30
* n mod 30 = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
@ -42,40 +41,38 @@ export async function randomProbablePrime(bits, e, k) {
*/
const adds = [1, 6, 5, 4, 3, 2, 1, 4, 3, 2, 1, 2, 1, 4, 3, 2, 1, 2, 1, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 2];
const n = await getRandomBigInteger(min, min.leftShift(one));
let i = n.mod(thirty).toNumber();
let n = getRandomBigInteger(min, min << _1n);
let i = bigIntToNumber(mod(n, _30n));
do {
n.iadd(new BigInteger(adds[i]));
n += BigInt(adds[i]);
i = (i + adds[i]) % adds.length;
// If reached the maximum, go back to the minimum.
if (n.bitLength() > bits) {
n.imod(min.leftShift(one)).iadd(min);
i = n.mod(thirty).toNumber();
if (bitLength(n) > bits) {
n = mod(n, min << _1n); n += min;
i = bigIntToNumber(mod(n, _30n));
}
} while (!await isProbablePrime(n, e, k));
} while (!isProbablePrime(n, e, k));
return n;
}
/**
* Probabilistic primality testing
* @param {BigInteger} n - Number to test
* @param {BigInteger} e - Optional RSA exponent to check against the prime
* @param {Integer} k - Optional number of iterations of Miller-Rabin test
* @returns {boolean}
* @async
* @param n - Number to test
* @param e - Optional RSA exponent to check against the prime
* @param k - Optional number of iterations of Miller-Rabin test
*/
export async function isProbablePrime(n, e, k) {
if (e && !n.dec().gcd(e).isOne()) {
export function isProbablePrime(n: bigint, e: bigint, k: number) {
if (e && gcd(n - _1n, e) !== _1n) {
return false;
}
if (!await divisionTest(n)) {
if (!divisionTest(n)) {
return false;
}
if (!await fermat(n)) {
if (!fermat(n)) {
return false;
}
if (!await millerRabin(n, k)) {
if (!millerRabin(n, k)) {
return false;
}
// TODO implement the Lucas test
@ -86,19 +83,16 @@ export async function isProbablePrime(n, e, k) {
/**
* Tests whether n is probably prime or not using Fermat's test with b = 2.
* Fails if b^(n-1) mod n != 1.
* @param {BigInteger} n - Number to test
* @param {BigInteger} b - Optional Fermat test base
* @returns {boolean}
* @param n - Number to test
* @param b - Optional Fermat test base
*/
export async function fermat(n, b) {
b = b || new BigInteger(2);
return b.modExp(n.dec(), n).isOne();
export function fermat(n: bigint, b = BigInt(2)) {
return modExp(b, n - _1n, n) === _1n;
}
export async function divisionTest(n) {
return smallPrimes.every(m => {
return n.mod(new BigInteger(m)) !== 0;
});
export function divisionTest(n: bigint) {
const _0n = BigInt(0);
return smallPrimes.every(m => mod(n, m) !== _0n);
}
// https://github.com/gpg/libgcrypt/blob/master/cipher/primegen.c
@ -182,7 +176,7 @@ const smallPrimes = [
4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889,
4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951,
4957, 4967, 4969, 4973, 4987, 4993, 4999
];
].map(n => BigInt(n));
// Miller-Rabin - Miller Rabin algorithm for primality test
@ -217,42 +211,42 @@ const smallPrimes = [
/**
* Tests whether n is probably prime or not using the Miller-Rabin test.
* See HAC Remark 4.28.
* @param {BigInteger} n - Number to test
* @param {Integer} k - Optional number of iterations of Miller-Rabin test
* @param {Function} rand - Optional function to generate potential witnesses
* @param n - Number to test
* @param k - Optional number of iterations of Miller-Rabin test
* @param rand - Optional function to generate potential witnesses
* @returns {boolean}
* @async
*/
export async function millerRabin(n, k, rand) {
const len = n.bitLength();
export function millerRabin(n: bigint, k: number, rand?: () => bigint) {
const len = bitLength(n);
if (!k) {
k = Math.max(1, (len / 48) | 0);
}
const n1 = n.dec(); // n - 1
const n1 = n - _1n; // n - 1
// Find d and s, (n - 1) = (2 ^ s) * d;
let s = 0;
while (!n1.getBit(s)) { s++; }
const d = n.rightShift(new BigInteger(s));
while (!getBit(n1, s)) { s++; }
const d = n >> BigInt(s);
for (; k > 0; k--) {
const a = rand ? rand() : await getRandomBigInteger(new BigInteger(2), n1);
const a = rand ? rand() : getRandomBigInteger(BigInt(2), n1);
let x = a.modExp(d, n);
if (x.isOne() || x.equal(n1)) {
let x = modExp(a, d, n);
if (x === _1n || x === n1) {
continue;
}
let i;
for (i = 1; i < s; i++) {
x = x.mul(x).mod(n);
x = mod(x * x, n);
if (x.isOne()) {
if (x === _1n) {
return false;
}
if (x.equal(n1)) {
if (x === n1) {
break;
}
}

View File

@ -25,10 +25,11 @@ import util from '../../util';
import { uint8ArrayToB64, b64ToUint8Array } from '../../encoding/base64';
import { emsaEncode, emeEncode, emeDecode } from '../pkcs1';
import enums from '../../enums';
import BigInteger from '../../biginteger';
import { bigIntToNumber, bigIntToUint8Array, bitLength, byteLength, mod, modExp, modInv, uint8ArrayToBigInt } from '../biginteger';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
const _1n = BigInt(1);
/** Create signature
* @param {module:enums.hash} hashAlgo - Hash algorithm
@ -142,14 +143,14 @@ export async function decrypt(data, n, e, d, p, q, u, randomPayload) {
* @async
*/
export async function generate(bits, e) {
e = new BigInteger(e);
e = BigInt(e);
// Native RSA keygen using Web Crypto
if (util.getWebCrypto()) {
const keyGenOpt = {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: bits, // the specified keysize in bits
publicExponent: e.toUint8Array(), // take three bytes (max 65537) for exponent
publicExponent: bigIntToUint8Array(e), // take three bytes (max 65537) for exponent
hash: {
name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify'
}
@ -164,7 +165,7 @@ export async function generate(bits, e) {
} else if (util.getNodeCrypto()) {
const opts = {
modulusLength: bits,
publicExponent: e.toNumber(),
publicExponent: bigIntToNumber(e),
publicKeyEncoding: { type: 'pkcs1', format: 'jwk' },
privateKeyEncoding: { type: 'pkcs1', format: 'jwk' }
};
@ -187,26 +188,26 @@ export async function generate(bits, e) {
let q;
let n;
do {
q = await randomProbablePrime(bits - (bits >> 1), e, 40);
p = await randomProbablePrime(bits >> 1, e, 40);
n = p.mul(q);
} while (n.bitLength() !== bits);
q = randomProbablePrime(bits - (bits >> 1), e, 40);
p = randomProbablePrime(bits >> 1, e, 40);
n = p * q;
} while (bitLength(n) !== bits);
const phi = p.dec().imul(q.dec());
const phi = (p - _1n) * (q - _1n);
if (q.lt(p)) {
if (q < p) {
[p, q] = [q, p];
}
return {
n: n.toUint8Array(),
e: e.toUint8Array(),
d: e.modInv(phi).toUint8Array(),
p: p.toUint8Array(),
q: q.toUint8Array(),
n: bigIntToUint8Array(n),
e: bigIntToUint8Array(e),
d: bigIntToUint8Array(modInv(e, phi)),
p: bigIntToUint8Array(p),
q: bigIntToUint8Array(q),
// dp: d.mod(p.subn(1)),
// dq: d.mod(q.subn(1)),
u: p.modInv(q).toUint8Array()
u: bigIntToUint8Array(modInv(p, q))
};
}
@ -222,24 +223,24 @@ export async function generate(bits, e) {
* @async
*/
export async function validateParams(n, e, d, p, q, u) {
n = new BigInteger(n);
p = new BigInteger(p);
q = new BigInteger(q);
n = uint8ArrayToBigInt(n);
p = uint8ArrayToBigInt(p);
q = uint8ArrayToBigInt(q);
// expect pq = n
if (!p.mul(q).equal(n)) {
if ((p * q) !== n) {
return false;
}
const two = new BigInteger(2);
const _2n = BigInt(2);
// expect p*u = 1 mod q
u = new BigInteger(u);
if (!p.mul(u).mod(q).isOne()) {
u = uint8ArrayToBigInt(u);
if (mod(p * u, q) !== BigInt(1)) {
return false;
}
e = new BigInteger(e);
d = new BigInteger(d);
e = uint8ArrayToBigInt(e);
d = uint8ArrayToBigInt(d);
/**
* In RSA pkcs#1 the exponents (d, e) are inverses modulo lcm(p-1, q-1)
* We check that [de = 1 mod (p-1)] and [de = 1 mod (q-1)]
@ -247,11 +248,11 @@ export async function validateParams(n, e, d, p, q, u) {
*
* We blind the multiplication with r, and check that rde = r mod lcm(p-1, q-1)
*/
const nSizeOver3 = new BigInteger(Math.floor(n.bitLength() / 3));
const r = await getRandomBigInteger(two, two.leftShift(nSizeOver3)); // r in [ 2, 2^{|n|/3} ) < p and q
const rde = r.mul(d).mul(e);
const nSizeOver3 = BigInt(Math.floor(bitLength(n) / 3));
const r = getRandomBigInteger(_2n, _2n << nSizeOver3); // r in [ 2, 2^{|n|/3} ) < p and q
const rde = r * d * e;
const areInverses = rde.mod(p.dec()).equal(r) && rde.mod(q.dec()).equal(r);
const areInverses = mod(rde, p - _1n) === r && mod(rde, q - _1n) === r;
if (!areInverses) {
return false;
}
@ -260,13 +261,13 @@ export async function validateParams(n, e, d, p, q, u) {
}
async function bnSign(hashAlgo, n, d, hashed) {
n = new BigInteger(n);
const m = new BigInteger(await emsaEncode(hashAlgo, hashed, n.byteLength()));
d = new BigInteger(d);
if (m.gte(n)) {
n = uint8ArrayToBigInt(n);
const m = uint8ArrayToBigInt(emsaEncode(hashAlgo, hashed, byteLength(n)));
d = uint8ArrayToBigInt(d);
if (m >= n) {
throw new Error('Message size cannot exceed modulus size');
}
return m.modExp(d, n).toUint8Array('be', n.byteLength());
return bigIntToUint8Array(modExp(m, d, n), 'be', byteLength(n));
}
async function webSign(hashName, data, n, e, d, p, q, u) {
@ -296,14 +297,14 @@ async function nodeSign(hashAlgo, data, n, e, d, p, q, u) {
}
async function bnVerify(hashAlgo, s, n, e, hashed) {
n = new BigInteger(n);
s = new BigInteger(s);
e = new BigInteger(e);
if (s.gte(n)) {
n = uint8ArrayToBigInt(n);
s = uint8ArrayToBigInt(s);
e = uint8ArrayToBigInt(e);
if (s >= n) {
throw new Error('Signature size cannot exceed modulus size');
}
const EM1 = s.modExp(e, n).toUint8Array('be', n.byteLength());
const EM2 = await emsaEncode(hashAlgo, hashed, n.byteLength());
const EM1 = bigIntToUint8Array(modExp(s, e, n), 'be', byteLength(n));
const EM2 = emsaEncode(hashAlgo, hashed, byteLength(n));
return util.equalsUint8Array(EM1, EM2);
}
@ -339,13 +340,13 @@ async function nodeEncrypt(data, n, e) {
}
async function bnEncrypt(data, n, e) {
n = new BigInteger(n);
data = new BigInteger(emeEncode(data, n.byteLength()));
e = new BigInteger(e);
if (data.gte(n)) {
n = uint8ArrayToBigInt(n);
data = uint8ArrayToBigInt(emeEncode(data, byteLength(n)));
e = uint8ArrayToBigInt(e);
if (data >= n) {
throw new Error('Message size cannot exceed modulus size');
}
return data.modExp(e, n).toUint8Array('be', n.byteLength());
return bigIntToUint8Array(modExp(data, e, n), 'be', byteLength(n));
}
async function nodeDecrypt(data, n, e, d, p, q, u) {
@ -360,34 +361,32 @@ async function nodeDecrypt(data, n, e, d, p, q, u) {
}
async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) {
data = new BigInteger(data);
n = new BigInteger(n);
e = new BigInteger(e);
d = new BigInteger(d);
p = new BigInteger(p);
q = new BigInteger(q);
u = new BigInteger(u);
if (data.gte(n)) {
data = uint8ArrayToBigInt(data);
n = uint8ArrayToBigInt(n);
e = uint8ArrayToBigInt(e);
d = uint8ArrayToBigInt(d);
p = uint8ArrayToBigInt(p);
q = uint8ArrayToBigInt(q);
u = uint8ArrayToBigInt(u);
if (data >= n) {
throw new Error('Data too large.');
}
const dq = d.mod(q.dec()); // d mod (q-1)
const dp = d.mod(p.dec()); // d mod (p-1)
const dq = mod(d, q - _1n); // d mod (q-1)
const dp = mod(d, p - _1n); // d mod (p-1)
const unblinder = (await getRandomBigInteger(new BigInteger(2), n)).mod(n);
const blinder = unblinder.modInv(n).modExp(e, n);
data = data.mul(blinder).mod(n);
const unblinder = getRandomBigInteger(BigInt(2), n);
const blinder = modExp(modInv(unblinder, n), e, n);
data = mod(data * blinder, n);
const mp = modExp(data, dp, p); // data**{d mod (q-1)} mod p
const mq = modExp(data, dq, q); // data**{d mod (p-1)} mod q
const h = mod(u * (mq - mp), q); // u * (mq-mp) mod q (operands already < q)
const mp = data.modExp(dp, p); // data**{d mod (q-1)} mod p
const mq = data.modExp(dq, q); // data**{d mod (p-1)} mod q
const h = u.mul(mq.sub(mp)).mod(q); // u * (mq-mp) mod q (operands already < q)
let result = h * p + mp; // result < n due to relations above
let result = h.mul(p).add(mp); // result < n due to relations above
result = mod(result * unblinder, n);
result = result.mul(unblinder).mod(n);
return emeDecode(result.toUint8Array('be', n.byteLength()), randomPayload);
return emeDecode(bigIntToUint8Array(result, 'be', byteLength(n)), randomPayload);
}
/** Convert Openpgp private key params to jwk key according to
@ -401,14 +400,14 @@ async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) {
* @param {Uint8Array} u
*/
async function privateToJWK(n, e, d, p, q, u) {
const pNum = new BigInteger(p);
const qNum = new BigInteger(q);
const dNum = new BigInteger(d);
const pNum = uint8ArrayToBigInt(p);
const qNum = uint8ArrayToBigInt(q);
const dNum = uint8ArrayToBigInt(d);
let dq = dNum.mod(qNum.dec()); // d mod (q-1)
let dp = dNum.mod(pNum.dec()); // d mod (p-1)
dp = dp.toUint8Array();
dq = dq.toUint8Array();
let dq = mod(dNum, qNum - _1n); // d mod (q-1)
let dp = mod(dNum, pNum - _1n); // d mod (p-1)
dp = bigIntToUint8Array(dp);
dq = bigIntToUint8Array(dq);
return {
kty: 'RSA',
n: uint8ArrayToB64(n, true),
@ -444,7 +443,7 @@ function publicToJWK(n, e) {
function jwkToPrivate(jwk, e) {
return {
n: b64ToUint8Array(jwk.n),
e: e.toUint8Array(),
e: bigIntToUint8Array(e),
d: b64ToUint8Array(jwk.d),
// switch p and q
p: b64ToUint8Array(jwk.q),

View File

@ -21,7 +21,7 @@
* @fileoverview Provides tools for retrieving secure randomness from browsers or Node.js
* @module crypto/random
*/
import BigInteger from '../biginteger';
import { byteLength, mod, uint8ArrayToBigInt } from './biginteger';
import util from '../util';
const nodeCrypto = util.getNodeCrypto();
@ -45,23 +45,23 @@ export function getRandomBytes(length) {
}
/**
* Create a secure random BigInteger that is greater than or equal to min and less than max.
* @param {module:BigInteger} min - Lower bound, included
* @param {module:BigInteger} max - Upper bound, excluded
* @returns {Promise<module:BigInteger>} Random BigInteger.
* Create a secure random BigInt that is greater than or equal to min and less than max.
* @param {bigint} min - Lower bound, included
* @param {bigint} max - Upper bound, excluded
* @returns {bigint} Random BigInt.
* @async
*/
export async function getRandomBigInteger(min, max) {
if (max.lt(min)) {
export function getRandomBigInteger(min, max) {
if (max < min) {
throw new Error('Illegal parameter value: max <= min');
}
const modulus = max.sub(min);
const bytes = modulus.byteLength();
const modulus = max - min;
const bytes = byteLength(modulus);
// Using a while loop is necessary to avoid bias introduced by the mod operation.
// However, we request 64 extra random bits so that the bias is negligible.
// Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
const r = new BigInteger(getRandomBytes(bytes + 8));
return r.mod(modulus).add(min);
const r = uint8ArrayToBigInt(getRandomBytes(bytes + 8));
return mod(r, modulus) + min;
}

96
test/crypto/biginteger.js Normal file
View File

@ -0,0 +1,96 @@
import { expect } from 'chai';
import BN from 'bn.js';
import { bigIntToUint8Array, bitLength, byteLength, gcd, getBit, modExp, modInv } from '../../src/crypto/biginteger';
import { getRandomBytes } from '../../src/crypto/random';
async function getRandomBN(min, max) {
if (max.cmp(min) <= 0) {
throw new Error('Illegal parameter value: max <= min');
}
const modulus = max.sub(min);
const bytes = modulus.byteLength();
const r = new BN(getRandomBytes(bytes + 8));
return r.mod(modulus).add(min);
}
export default () => describe('BigInt', () => {
it('bitLength is correct', function() {
const n = BigInt(127);
const expected = 7;
expect(bitLength(n)).to.equal(expected);
expect(bitLength(n + BigInt(1))).to.equal(expected + 1);
});
it('byteLength is correct', function() {
const n = BigInt(65535);
const expected = 2;
expect(byteLength(n)).to.equal(expected);
expect(byteLength(n + BigInt(1))).to.equal(expected + 1);
});
it('toUint8Array is correct', function() {
const nString = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027';
const n = BigInt(nString);
const paddedSize = Number(byteLength(n)) + 1;
// big endian, unpadded
let expected = new BN(nString).toArrayLike(Uint8Array);
expect(bigIntToUint8Array(n)).to.deep.equal(expected);
// big endian, padded
expected = new BN(nString).toArrayLike(Uint8Array, 'be', paddedSize);
expect(bigIntToUint8Array(n, 'be', paddedSize)).to.deep.equal(expected);
// little endian, unpadded
expected = new BN(nString).toArrayLike(Uint8Array, 'le');
expect(bigIntToUint8Array(n, 'le')).to.deep.equal(expected);
//little endian, padded
expected = new BN(nString).toArrayLike(Uint8Array, 'le', paddedSize);
expect(bigIntToUint8Array(n, 'le', paddedSize)).to.deep.equal(expected);
});
it('modExp is correct (large values)', function() {
const stringX = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027';
const stringE = '21139356010872569239159922781526379521587348169074209285187910481667533072168468011617194695181255483288792585413365359733692097084373249198758148704369207793873998901870577262254971784191473102265830193058813215898765238784670469696574407580179153118937858890572095234316482449291777882525949871374961971753';
const stringN = '129189808515414783602892982235788912674846062846614219472827821758734760420002631653235573915244294540972376140705505703576175711417114803419704967903726436285518767606681184247119430411311152556442947708732584954518890222684529678365388350886907287414896703685680210648760841628375425909680236584021041565183';
const x = BigInt(stringX);
const e = BigInt(stringE);
const n = BigInt(stringN);
const got = modExp(x, e, n);
const expected = new BN(stringX).toRed(BN.red(new BN(stringN))).redPow(new BN(stringE));
// different formats, it's easier to compare strings
expect(got.toString(), expected.toString());
});
it('gcd is correct', async function() {
const aBN = await getRandomBN(new BN(2), new BN(200));
const bBN = await getRandomBN(new BN(2), new BN(200));
if (aBN.isEven()) aBN.iaddn(1);
const a = BigInt(aBN.toString());
const b = BigInt(bBN.toString());
const expected = aBN.gcd(bBN);
expect(gcd(a, b).toString()).to.equal(expected.toString());
});
it('modular inversion is correct', async function() {
const moduloBN = new BN(229); // this is a prime
const baseBN = await getRandomBN(new BN(2), moduloBN);
const a = BigInt(baseBN.toString());
const n = BigInt(moduloBN.toString());
const expected = baseBN.invm(moduloBN);
expect(modInv(a, n).toString()).to.equal(expected.toString());
// test negative operand
const expectedNegated = baseBN.neg().invm(moduloBN);
expect(modInv(-a, n).toString()).to.equal(expectedNegated.toString());
expect(() => modInv(a * n, n)).to.throw(/Inverse does not exist/);
});
it('getBit is correct', async function() {
const i = 5;
const nBN = await getRandomBN(new BN(2), new BN(200));
const n = BigInt(nBN.toString());
const expected = nBN.testn(5) ? 1 : 0;
expect(getBit(n, i)).to.equal(expected);
});
});

View File

@ -1,3 +1,4 @@
import testBigInteger from './biginteger';
import testCipher from './cipher';
import testHash from './hash';
import testCrypto from './crypto';
@ -14,6 +15,7 @@ import testRSA from './rsa';
import testValidate from './validate';
export default () => describe('Crypto', function () {
testBigInteger();
testCipher();
testHash();
testCrypto();

View File

@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/new
chaiUse(chaiAsPromised);
import openpgp from '../initOpenpgp.js';
import BigInteger from '../../src/biginteger.ts';
import { bigIntToUint8Array, modExp, uint8ArrayToBigInt } from '../../src/crypto/biginteger.ts';
const armoredDSAKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
@ -390,11 +390,12 @@ export default () => {
const keyPacket = await cloneKeyPacket(egKey);
const { p, g } = keyPacket.publicParams;
const pBN = new BigInteger(p);
const gBN = new BigInteger(g);
const _1n = BigInt(1);
const pBN = uint8ArrayToBigInt(p);
const gBN = uint8ArrayToBigInt(g);
// g**(p-1)/2 has order 2
const gOrd2 = gBN.modExp(pBN.dec().irightShift(new BigInteger(1)), pBN);
keyPacket.publicParams.g = gOrd2.toUint8Array();
const gOrd2 = modExp(gBN, (pBN - _1n) >> _1n, pBN);
keyPacket.publicParams.g = bigIntToUint8Array(gOrd2);
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
});
});

View File

@ -7,6 +7,6 @@
"allowJs": true,
"paths": {
"openpgp": [ "." ]
},
},
}
}