UNPKG

micro-rsa-dsa-dh

Version:

Minimal implementation of older cryptography algorithms: RSA, DSA, DH, ElGamal

108 lines 4.49 kB
import { randomBytes } from '@noble/hashes/utils.js'; import { isProbablySafePrime } from "./primality.js"; import { bytesToNumber, gcd, invert, mod, pow } from "./utils.js"; /** * Returns random number in range [min, max) */ function randomBigInt(bytes, min, max, randFn = randomBytes) { let res; do res = bytesToNumber(randFn(bytes)); while (res < min || res >= max); // Key [2, p-1) return res; } export function genElGamalParams(bits) { if (!Number.isSafeInteger(bits) || bits <= 0 || bits % 8 !== 0) throw new Error('number of bits should be positive integer aligned to byte boundary'); // 512: 1s, 1024: 20s, 2048: 1046s let p = 0n; do p = bytesToNumber(randomBytes(bits / 8)); while (!isProbablySafePrime(p, 10)); // NOTE: this is very slow! const q = (p - 1n) >> 1n; while (true) { // g=2 -> Bleichenbacher's attack const g = randomBigInt(bits / 8, 3n, p); if (pow(g, 2n, p) === 1n) continue; if (pow(g, q, p) === 1n) continue; if ((p - 1n) % g === 0n) continue; const gInv = invert(g, p); // Khadir's attack if ((p - 1n) % gInv === 0n) continue; return { p, g }; } } export const ElGamal = ({ p, g }) => { if (typeof p !== 'bigint' || typeof g !== 'bigint') throw new Error('wrong params'); if (g <= 1n || g >= p) throw new Error('g should be in the range 1 < g < p'); const pBytes = p.toString(16).length / 2; return { randomPrivateKey() { return randomBigInt(pBytes, 2n, p - 1n); // [2, p-1) }, getPublicKey(privateKey) { if (typeof privateKey !== 'bigint') throw new Error('privateKey should be bigint'); return pow(g, privateKey, p); }, encrypt(publicKey, message, nonce) { if (typeof publicKey !== 'bigint') throw new Error('publicKey should be bigint'); if (typeof message !== 'bigint') throw new Error('wrong message'); if (nonce === undefined) nonce = randomBigInt(pBytes, 1n, p - 1n); if (typeof nonce !== 'bigint' || nonce <= 0n || nonce >= p - 1n) throw new Error(`invalid nonce=${nonce}`); const c1 = pow(g, nonce, p); // c1 = g^k mod p const yk = pow(publicKey, nonce, p); // c2 = m * (y^k mod p) mod p const c2 = (message * yk) % p; return { ct1: c1, ct2: c2 }; }, decrypt(privateKey, ciphertext) { if (typeof privateKey !== 'bigint') throw new Error('privateKey should be bigint'); if (typeof ciphertext.ct1 !== 'bigint' || typeof ciphertext.ct2 !== 'bigint') throw new Error('invalid ciphertext'); // Decryption process const c1x = pow(ciphertext.ct1, privateKey, p); // c1^x mod p const invC1x = invert(c1x, p); // (c1^x)^-1 mod p const m = (ciphertext.ct2 * invC1x) % p; // (c2 * (c1^x)^-1) mod p return m; }, sign(privateKey, message, nonce) { if (typeof privateKey !== 'bigint') throw new Error('privateKey should be bigint'); if (typeof message !== 'bigint') throw new Error('wrong message'); if (nonce === undefined) { do nonce = randomBigInt(pBytes, 1n, p - 1n); while (gcd(nonce, p - 1n) !== 1n); // there is no invert otherwise } if (typeof nonce !== 'bigint' || nonce <= 0n || nonce >= p - 1n) throw new Error(`invalid nonce=${nonce}`); const r = pow(g, nonce, p); const kInv = invert(nonce, p - 1n); const s = mod(kInv * (message - privateKey * r), p - 1n); return { r, s }; }, verify(publicKey, message, sig) { if (typeof publicKey !== 'bigint') throw new Error('publicKey should be bigint'); if (typeof sig.r !== 'bigint' || typeof sig.s !== 'bigint') throw new Error('invalid signature'); const gH = pow(g, message, p); const yR = pow(publicKey, sig.r, p); const rS = pow(sig.r, sig.s, p); const yRrS = mod(yR * rS, p); return gH === yRrS; }, }; }; //# sourceMappingURL=elgamal.js.map