From 7988e5134e519c96805e6ceeba5bb10c96023941 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Fri, 16 Dec 2022 18:36:33 +0000 Subject: [PATCH] Add experimental Argon2 implementation from RFC9106. --- src/argon2.ts | 377 ++++++++++++++++++++++++++++++++++++ test/argon2.test.js | 360 ++++++++++++++++++++++++++++++++++ test/benchmark/argon.js | 62 ++++++ test/benchmark/package.json | 3 + 4 files changed, 802 insertions(+) create mode 100644 src/argon2.ts create mode 100644 test/argon2.test.js create mode 100644 test/benchmark/argon.js diff --git a/src/argon2.ts b/src/argon2.ts new file mode 100644 index 0000000..f5e03a8 --- /dev/null +++ b/src/argon2.ts @@ -0,0 +1,377 @@ +import assert from './_assert.js'; +import { Input, toBytes, u8, u32 } from './utils'; +import { blake2b } from './blake2b'; +import u64 from './_u64.js'; + +// Experimental implementation of argon2. +// Could be broken & slow. May be removed at a later time. +// RFC 9106 + +enum Types { + Argond2d = 0, + Argon2i = 1, + Argon2id = 2, +} + +const ARGON2_SYNC_POINTS = 4; + +const toBytesOptional = (buf?: Input) => (buf !== undefined ? toBytes(buf) : new Uint8Array([])); + +function mul(a: number, b: number) { + const aL = a & 0xffff; + const aH = a >>> 16; + const bL = b & 0xffff; + const bH = b >>> 16; + const ll = Math.imul(aL, bL); + const hl = Math.imul(aH, bL); + const lh = Math.imul(aL, bH); + const hh = Math.imul(aH, bH); + const BUF = ((ll >>> 16) + (hl & 0xffff) + lh) | 0; + const h = ((hl >>> 16) + (BUF >>> 16) + hh) | 0; + return { h, l: (BUF << 16) | (ll & 0xffff) }; +} + +function relPos(areaSize: number, relativePos: number) { + // areaSize - 1 - ((areaSize * ((relativePos ** 2) >>> 32)) >>> 32) + return areaSize - 1 - mul(areaSize, mul(relativePos, relativePos).h).h; +} + +function mul2(a: number, b: number) { + // 2 * a * b (via shifts) + const { h, l } = mul(a, b); + return { h: ((h << 1) | (l >>> 31)) & 0xffff_ffff, l: (l << 1) & 0xffff_ffff }; +} + +function blamka(Ah: number, Al: number, Bh: number, Bl: number) { + const { h: Ch, l: Cl } = mul2(Al, Bl); + // A + B + (2 * A * B) + const Rll = u64.add3L(Al, Bl, Cl); + return { h: u64.add3H(Rll, Ah, Bh, Ch), l: Rll | 0 }; +} + +// Temporary block buffer +const BUF = new Uint32Array(256); + +function G(a: number, b: number, c: number, d: number) { + let Al = BUF[2*a], Ah = BUF[2*a + 1]; // prettier-ignore + let Bl = BUF[2*b], Bh = BUF[2*b + 1]; // prettier-ignore + let Cl = BUF[2*c], Ch = BUF[2*c + 1]; // prettier-ignore + let Dl = BUF[2*d], Dh = BUF[2*d + 1]; // prettier-ignore + + ({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl)); + ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al }); + ({ Dh, Dl } = { Dh: u64.rotr32H(Dh, Dl), Dl: u64.rotr32L(Dh, Dl) }); + + ({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl)); + ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl }); + ({ Bh, Bl } = { Bh: u64.rotrSH(Bh, Bl, 24), Bl: u64.rotrSL(Bh, Bl, 24) }); + + ({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl)); + ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al }); + ({ Dh, Dl } = { Dh: u64.rotrSH(Dh, Dl, 16), Dl: u64.rotrSL(Dh, Dl, 16) }); + + ({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl)); + ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl }); + ({ Bh, Bl } = { Bh: u64.rotrBH(Bh, Bl, 63), Bl: u64.rotrBL(Bh, Bl, 63) }); + + (BUF[2 * a] = Al), (BUF[2 * a + 1] = Ah); + (BUF[2 * b] = Bl), (BUF[2 * b + 1] = Bh); + (BUF[2 * c] = Cl), (BUF[2 * c + 1] = Ch); + (BUF[2 * d] = Dl), (BUF[2 * d + 1] = Dh); +} + +// prettier-ignore +function P( + v00: number, v01: number, v02: number, v03: number, v04: number, v05: number, v06: number, v07: number, + v08: number, v09: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number, +) { + G(v00, v04, v08, v12); + G(v01, v05, v09, v13); + G(v02, v06, v10, v14); + G(v03, v07, v11, v15); + G(v00, v05, v10, v15); + G(v01, v06, v11, v12); + G(v02, v07, v08, v13); + G(v03, v04, v09, v14); +} + +function block(x: Uint32Array, xPos: number, yPos: number, outPos: number, needXor: boolean) { + for (let i = 0; i < 256; i++) BUF[i] = x[xPos + i] ^ x[yPos + i]; + + // columns + for (let i = 0; i < 128; i += 16) { + // prettier-ignore + P( + i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, + i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15 + ); + } + // rows + for (let i = 0; i < 16; i += 2) { + // prettier-ignore + P( + i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49, + i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113 + ); + } + + if (needXor) for (let i = 0; i < 256; i++) x[outPos + i] ^= BUF[i] ^ x[xPos + i] ^ x[yPos + i]; + else for (let i = 0; i < 256; i++) x[outPos + i] = BUF[i] ^ x[xPos + i] ^ x[yPos + i]; +} + +// Variable-Length Hash Function H' +function Hp(A: Uint32Array, dkLen: number) { + const A8 = u8(A); + const T = new Uint32Array(1); + const T8 = u8(T); + T[0] = dkLen; + // Fast path + if (dkLen <= 64) return blake2b.create({ dkLen }).update(T8).update(A8).digest(); + const out = new Uint8Array(dkLen); + let V = blake2b.create({}).update(T8).update(A8).digest(); + let pos = 0; + // First block + out.set(V.subarray(0, 32)); + pos += 32; + // Rest blocks + for (; dkLen - pos > 64; pos += 32) out.set((V = blake2b(V)).subarray(0, 32), pos); + // Last block + out.set(blake2b(V, { dkLen: dkLen - pos }), pos); + return u32(out); +} + +function indexAlpha( + r: number, + s: number, + laneLen: number, + segmentLen: number, + index: number, + randL: number, + sameLane: boolean = false +) { + let area; + if (0 == r) { + if (0 == s) area = index - 1; + else if (sameLane) area = s * segmentLen + index - 1; + else area = s * segmentLen + (index == 0 ? -1 : 0); + } else if (sameLane) area = laneLen - segmentLen + index - 1; + else area = laneLen - segmentLen + (index == 0 ? -1 : 0); + const startPos = r !== 0 && s !== ARGON2_SYNC_POINTS - 1 ? (s + 1) * segmentLen : 0; + const rel = relPos(area, randL); + // NOTE: check about overflows here + // absPos = (startPos + relPos) % laneLength; + return (startPos + rel) % laneLen; +} + +// RFC 9106 +export type ArgonOpts = { + t: number; // Time cost, iterations count + m: number; // Memory cost (in KB) + p: number; // Parallelization parameter + version?: number; // Default: 0x13 (19) + key?: Input; // Optional key + personalization?: Input; // Optional arbitrary extra data + dkLen?: number; // Desired number of returned bytes + asyncTick?: number; // Maximum time in ms for which async function can block execution + maxmem?: number; + onProgress?: (progress: number) => void; +}; + +function argon2Init(type: Types, password: Input, salt: Input, opts: ArgonOpts) { + password = toBytes(password); + salt = toBytes(salt); + let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress } = { + ...opts, + version: opts.version || 0x13, + dkLen: opts.dkLen || 32, + maxmem: 2 ** 32, + }; + // Validation + assert.number(p); + assert.number(dkLen); + assert.number(m); + assert.number(t); + assert.number(version); + if (dkLen < 4 || dkLen >= 2 ** 32) throw new Error('Argon2: dkLen should be at least 4 bytes'); + if (dkLen < 1 || p >= 2 ** 32) throw new Error('Argon2: p (paralllelism) should be at least 1'); + if (dkLen < 1 || p >= 2 ** 32) throw new Error('Argon2: t (iterations) should be at least 1'); + if (m < 8 * p) throw new Error(`Argon2: memory should be at least 8*p bytes`); + if (version !== 16 && version !== 19) throw new Error(`Argon2: unknown version=${version}`); + password = toBytes(password); + if (password.length < 0 || password.length >= 2 ** 32) + throw new Error('Argon2: password should be less than 4 GB'); + salt = toBytes(salt); + if (salt.length < 8) throw new Error('Argon2: salt should be at least 8 bytes'); + key = toBytesOptional(key); + personalization = toBytesOptional(personalization); + if (onProgress !== undefined && typeof onProgress !== 'function') + throw new Error('progressCb should be function'); + // Params + const lanes = p; + // m' = 4 * p * floor (m / 4p) + const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p)); + //q = m' / p columns + const laneLen = Math.floor(mP / p); + const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS); + // H0 + const h = blake2b.create({}); + const BUF = new Uint32Array(1); + const BUF8 = u8(BUF); + for (const i of [p, dkLen, m, t, version, type]) { + if (i < 0 || i >= 2 ** 32) throw new Error(`Argon2: wrong parameter=${i}, expected uint32`); + BUF[0] = i; + h.update(BUF8); + } + for (let i of [password, salt, key, personalization]) { + BUF[0] = i.length; + h.update(BUF8).update(i); + } + const H0 = new Uint32Array(18); + const H0_8 = u8(H0); + h.digestInto(H0_8); + + // 256 u32 = 1024 (BLOCK_SIZE) + const memUsed = mP * 256; + if (memUsed < 0 || memUsed >= 2 ** 32 || memUsed > maxmem) { + throw new Error( + `Argon2: wrong params (memUsed=${memUsed} maxmem=${maxmem}), should be less than 2**32` + ); + } + const B = new Uint32Array(memUsed); + // Fill first blocks + for (let l = 0; l < p; l++) { + const i = 256 * laneLen * l; + // B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i)) + H0[17] = l; + H0[16] = 0; + B.set(Hp(H0, 1024), i); + // B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i)) + H0[16] = 1; + B.set(Hp(H0, 1024), i + 256); + } + let perBlock = () => {}; + if (onProgress) { + const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen; + // Invoke callback if progress changes from 10.01 to 10.02 + // Allows to draw smooth progress bar on up to 8K screen + const callbackPer = Math.max(Math.floor(totalBlock / 10000), 1); + let blockCnt = 0; + perBlock = () => { + blockCnt++; + if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock)) + onProgress(blockCnt / totalBlock); + }; + } + return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock }; +} + +function argon2Output(B: Uint32Array, p: number, laneLen: number, dkLen: number) { + const B_final = new Uint32Array(256); + for (let l = 0; l < p; l++) + for (let j = 0; j < 256; j++) B_final[j] ^= B[256 * (laneLen * l + laneLen - 1) + j]; + return u8(Hp(B_final, dkLen)); +} + +function processBlock( + B: Uint32Array, + address: Uint32Array, + l: number, + r: number, + s: number, + index: number, + laneLen: number, + segmentLen: number, + lanes: number, + offset: number, + prev: number, + dataIndependent: boolean, + needXor: boolean +) { + if (offset % laneLen) prev = offset - 1; + let randL, randH; + if (dataIndependent) { + if (index % 128 === 0) { + address[256 + 12]++; + block(address, 256, 2 * 256, 0, false); + block(address, 0, 2 * 256, 0, false); + } + randL = address[2 * (index % 128)]; + randH = address[2 * (index % 128) + 1]; + } else { + const T = 256 * prev; + randL = B[T]; + randH = B[T + 1]; + } + // address block + const refLane = r === 0 && s === 0 ? l : randH % lanes; + const refPos = indexAlpha(r, s, laneLen, segmentLen, index, randL, refLane == l); + const refBlock = laneLen * refLane + refPos; + // B[i][j] = G(B[i][j-1], B[l][z]) + block(B, 256 * prev, 256 * refBlock, offset * 256, needXor); +} + +function argon2(type: Types, password: Input, salt: Input, opts: ArgonOpts) { + const { mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock } = argon2Init( + type, + password, + salt, + opts + ); + // Pre-loop setup + // [address, input, zero_block] format so we can pass single U32 to block function + const address = new Uint32Array(3 * 256); + address[256 + 6] = mP; + address[256 + 8] = t; + address[256 + 10] = type; + for (let r = 0; r < t; r++) { + const needXor = r !== 0 && version === 0x13; + address[256 + 0] = r; + for (let s = 0; s < ARGON2_SYNC_POINTS; s++) { + address[256 + 4] = s; + const dataIndependent = type == Types.Argon2i || (type == Types.Argon2id && r === 0 && s < 2); + for (let l = 0; l < p; l++) { + address[256 + 2] = l; + address[256 + 12] = 0; + let startPos = 0; + if (r === 0 && s === 0) { + startPos = 2; + if (dataIndependent) { + address[256 + 12]++; + block(address, 256, 2 * 256, 0, false); + block(address, 0, 2 * 256, 0, false); + } + } + // current block postion + let offset = l * laneLen + s * segmentLen + startPos; + // previous block position + let prev = offset % laneLen ? offset - 1 : offset + laneLen - 1; + for (let index = startPos; index < segmentLen; index++, offset++, prev++) { + perBlock(); + processBlock( + B, + address, + l, + r, + s, + index, + laneLen, + segmentLen, + lanes, + offset, + prev, + dataIndependent, + needXor + ); + } + } + } + } + return argon2Output(B, p, laneLen, dkLen); +} + +export const argon2d = (password: Input, salt: Input, opts: ArgonOpts) => + argon2(Types.Argond2d, password, salt, opts); +export const argon2i = (password: Input, salt: Input, opts: ArgonOpts) => + argon2(Types.Argon2i, password, salt, opts); +export const argon2id = (password: Input, salt: Input, opts: ArgonOpts) => + argon2(Types.Argon2id, password, salt, opts); diff --git a/test/argon2.test.js b/test/argon2.test.js new file mode 100644 index 0000000..625dc94 --- /dev/null +++ b/test/argon2.test.js @@ -0,0 +1,360 @@ +const assert = require('assert'); +const crypto = require('crypto'); +const { should } = require('micro-should'); +const { argon2i, argon2d, argon2id } = require('../argon2'); +const { hexToBytes } = require('./utils'); +const { bytesToHex } = require('../utils'); + +const VECTORS = [ + { + fn: argon2i, + password: 'password', + salt: 'saltysaltsaltysalt', + m: 16, + p: 2, + t: 2, + exp: 'a78e02964b20e856927be1a1ba5a6bffd700dc84e9e82f5e926600c8896ee2ce', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/kats/argon2d + { + fn: argon2d, + version: 19, + password: hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'), + salt: hexToBytes('02020202020202020202020202020202'), + secret: hexToBytes('0303030303030303'), + data: hexToBytes('040404040404040404040404'), + m: 32, + t: 3, + p: 4, + exp: '512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/kats/argon2d_v16 + { + fn: argon2d, + version: 16, + password: hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'), + salt: hexToBytes('02020202020202020202020202020202'), + secret: hexToBytes('0303030303030303'), + data: hexToBytes('040404040404040404040404'), + m: 32, + t: 3, + p: 4, + exp: '96a9d4e5a1734092c85e29f410a45914a5dd1f5cbf08b2670da68a0285abf32b', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/kats/argon2i + { + fn: argon2i, + version: 19, + password: hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'), + salt: hexToBytes('02020202020202020202020202020202'), + secret: hexToBytes('0303030303030303'), + data: hexToBytes('040404040404040404040404'), + m: 32, + t: 3, + p: 4, + exp: 'c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/kats/argon2i_v16 + { + fn: argon2i, + version: 16, + password: hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'), + salt: hexToBytes('02020202020202020202020202020202'), + secret: hexToBytes('0303030303030303'), + data: hexToBytes('040404040404040404040404'), + m: 32, + t: 3, + p: 4, + exp: '87aeedd6517ab830cd9765cd8231abb2e647a5dee08f7c05e02fcb763335d0fd', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/kats/argon2id + { + fn: argon2id, + version: 19, + password: hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'), + salt: hexToBytes('02020202020202020202020202020202'), + secret: hexToBytes('0303030303030303'), + data: hexToBytes('040404040404040404040404'), + m: 32, + t: 3, + p: 4, + exp: '0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/kats/argon2id_v16 + { + fn: argon2id, + version: 16, + password: hexToBytes('0101010101010101010101010101010101010101010101010101010101010101'), + salt: hexToBytes('02020202020202020202020202020202'), + secret: hexToBytes('0303030303030303'), + data: hexToBytes('040404040404040404040404'), + m: 32, + t: 3, + p: 4, + exp: 'b64615f07789b66b645b67ee9ed3b377ae350b6bfcbb0fc95141ea8f322613c0', + }, + // https://github.com/P-H-C/phc-winner-argon2/blob/master/src/test.c + { + fn: argon2i, + version: 0x10, + t: 2, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694', + }, + // { + // fn: argon2i, + // version: 0x10, + // t: 2, + // m: 1048576, + // p: 1, + // password: 'password', + // salt: 'somesalt', + // exp: '9690ec55d28d3ed32562f2e73ea62b02b018757643a2ae6e79528459de8106e9', + // }, + { + fn: argon2i, + version: 0x10, + t: 2, + m: 262144, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '3e689aaa3d28a77cf2bc72a51ac53166761751182f1ee292e3f677a7da4c2467', + }, + { + fn: argon2i, + version: 0x10, + t: 2, + m: 256, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'fd4dd83d762c49bdeaf57c47bdcd0c2f1babf863fdeb490df63ede9975fccf06', + }, + { + fn: argon2i, + version: 0x10, + t: 2, + m: 256, + p: 2, + password: 'password', + salt: 'somesalt', + exp: 'b6c11560a6a9d61eac706b79a2f97d68b4463aa3ad87e00c07e2b01e90c564fb', + }, + { + fn: argon2i, + version: 0x10, + t: 1, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '81630552b8f3b1f48cdb1992c4c678643d490b2b5eb4ff6c4b3438b5621724b2', + }, + { + fn: argon2i, + version: 0x10, + t: 4, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'f212f01615e6eb5d74734dc3ef40ade2d51d052468d8c69440a3a1f2c1c2847b', + }, + { + fn: argon2i, + version: 0x10, + t: 2, + m: 65536, + p: 1, + password: 'differentpassword', + salt: 'somesalt', + exp: 'e9c902074b6754531a3a0be519e5baf404b30ce69b3f01ac3bf21229960109a3', + }, + { + fn: argon2i, + version: 0x10, + t: 2, + m: 65536, + p: 1, + password: 'password', + salt: 'diffsalt', + exp: '79a103b90fe8aef8570cb31fc8b22259778916f8336b7bdac3892569d4f1c497', + }, + { + fn: argon2i, + t: 2, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0', + }, + { + fn: argon2i, + t: 2, + m: 1048576, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'd1587aca0922c3b5d6a83edab31bee3c4ebaef342ed6127a55d19b2351ad1f41', + }, + { + fn: argon2i, + t: 2, + m: 262144, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '296dbae80b807cdceaad44ae741b506f14db0959267b183b118f9b24229bc7cb', + }, + { + fn: argon2i, + t: 2, + m: 256, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '89e9029f4637b295beb027056a7336c414fadd43f6b208645281cb214a56452f', + }, + { + fn: argon2i, + t: 2, + m: 256, + p: 2, + password: 'password', + salt: 'somesalt', + exp: '4ff5ce2769a1d7f4c8a491df09d41a9fbe90e5eb02155a13e4c01e20cd4eab61', + }, + // { + // // TEST + // fn: argon2i, + // t: 1, + // m: 65536, + // p: 1, + // password: 'password', + // salt: 'somesalt', + // exp: 'd168075c4d985e13ebeae560cf8b94c3b5d8a16c51916b6f4ac2da3ac11bbecf', + // }, + { + fn: argon2i, + t: 4, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'aaa953d58af3706ce3df1aefd4a64a84e31d7f54175231f1285259f88174ce5b', + }, + { + fn: argon2i, + t: 2, + m: 65536, + p: 1, + password: 'differentpassword', + salt: 'somesalt', + exp: '14ae8da01afea8700c2358dcef7c5358d9021282bd88663a4562f59fb74d22ee', + }, + { + fn: argon2i, + t: 2, + m: 65536, + p: 1, + password: 'password', + salt: 'diffsalt', + exp: 'b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271', + }, + { + fn: argon2id, + t: 2, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7', + }, + { + fn: argon2id, + t: 2, + m: 262144, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c', + }, + { + fn: argon2id, + t: 2, + m: 256, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe', + }, + { + fn: argon2id, + t: 2, + m: 256, + p: 2, + password: 'password', + salt: 'somesalt', + exp: '6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037', + }, + { + fn: argon2id, + t: 1, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: 'f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98', + }, + { + fn: argon2id, + t: 4, + m: 65536, + p: 1, + password: 'password', + salt: 'somesalt', + exp: '9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c', + }, + { + fn: argon2id, + t: 2, + m: 65536, + p: 1, + password: 'differentpassword', + salt: 'somesalt', + exp: '0b84d652cf6b0c4beaef0dfe278ba6a80df6696281d7e0d2891b817d8c458fde', + }, + { + fn: argon2id, + t: 2, + m: 65536, + p: 1, + password: 'password', + salt: 'diffsalt', + exp: 'bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c', + }, +]; + +for (let i = 0; i < VECTORS.length; i++) { + const v = VECTORS[i]; + should(`${v.fn.name}/${v.version || 0x13} (${i})`, () => { + const res = bytesToHex( + v.fn(v.password, v.salt, { + m: v.m, + p: v.p, + t: v.t, + key: v.secret, + personalization: v.data, + version: v.version, + }) + ); + assert.deepStrictEqual(res, v.exp); + }); +} + +if (require.main === module) should.run(); diff --git a/test/benchmark/argon.js b/test/benchmark/argon.js new file mode 100644 index 0000000..147b75c --- /dev/null +++ b/test/benchmark/argon.js @@ -0,0 +1,62 @@ +const bench = require('micro-bmark'); +const { run, mark } = bench; // or bench.mark +const crypto = require('crypto'); +// Noble +const { argon2i, argon2id, argon2d } = require('../../argon2'); +const wasm = require('hash-wasm'); +const sodium = require('libsodium-wrappers'); + +const ONLY_NOBLE = process.argv[2] === 'noble'; +const password = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]); +const salt = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + +const ITERS = [1, 4, 8]; +const MEMORY = [256, 64 * 1024, 256 * 1024, 1 * 1024 * 1024]; // in KB (256kb, 64mb, 256mb, 1gb) + +const KDF = { + argon2id: { + 'hash-wasm': (iters, mem) => + wasm.argon2id({ + password, + salt, + iterations: iters, + parallelism: 1, + hashLength: 32, + memorySize: mem, + outputType: 'binary', + }), + sodium: (iters, mem) => + sodium.crypto_pwhash( + 32, + password, + salt, + iters, + mem * 1024, + sodium.crypto_pwhash_ALG_ARGON2ID13 + ), + noble: (iters, mem) => argon2id(password, salt, { t: iters, m: mem, p: 1, dkLen: 32 }), + }, +}; + +const main = () => + run(async () => { + await sodium.ready; + for (const i of ITERS) { + for (const m of MEMORY) { + for (let [k, libs] of Object.entries(KDF)) { + const title = `${k} (memory: ${m} KB, iters: ${i})`; + if (!ONLY_NOBLE) console.log(`==== ${title} ====`); + for (const [lib, fn] of Object.entries(libs)) { + if (ONLY_NOBLE && lib !== 'noble') continue; + await mark(!ONLY_NOBLE ? lib : title, 10, () => fn(i, m)); + } + if (!ONLY_NOBLE) console.log(); + } + } + } + // Log current RAM + bench.logMem(); + }); + +module.exports = { main }; +if (require.main === module) main(); diff --git a/test/benchmark/package.json b/test/benchmark/package.json index 3b875e7..64f760e 100644 --- a/test/benchmark/package.json +++ b/test/benchmark/package.json @@ -30,5 +30,8 @@ "scrypt-async": "^2.0.1", "scrypt-js": "^3.0.1", "sha3": "^2.1.4" + }, + "dependencies": { + "libsodium-wrappers": "^0.7.10" } }