dsh-vault 1.3.0 → 1.4.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/argon2.js +402 -0
- package/lib/blake2b.js +212 -0
- package/lib/chacha20.js +79 -0
- package/lib/chrome.js +165 -33
- package/lib/client.js +1469 -953
- package/lib/client.js.map +1 -1
- package/lib/der.js +54 -0
- package/lib/firefox.js +141 -0
- package/lib/imports.js +428 -0
- package/lib/index.js +868 -9
- package/lib/kdbx.js +557 -0
- package/lib/salsa20.js +91 -0
- package/lib/zip.js +78 -0
- package/package.json +1 -1
package/lib/argon2.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argon2 key derivation (RFC 9106) for dsh-vault — used by KDBX4 Argon2-KDF
|
|
3
|
+
* databases (KeePassXC / KeePass 2.55+ default).
|
|
4
|
+
*
|
|
5
|
+
* Pure-JS implementation of Argon2d / Argon2id (version 0x13), closely
|
|
6
|
+
* following the reference implementation (P-H-C/phc-winner-argon2):
|
|
7
|
+
* - fill_block / fill_segment / next_addresses
|
|
8
|
+
* - index_alpha reference-block selection
|
|
9
|
+
* - fBlaMka mixing with 64-bit multiply via 16-bit limbs
|
|
10
|
+
* Memory blocks are 1024-byte buffers of 128 little-endian 64-bit words.
|
|
11
|
+
*
|
|
12
|
+
* References: RFC 9106; phc-winner-argon2 core.c / blamka-round-ref.h;
|
|
13
|
+
* KeePassXC src/crypto/kdf/Argon2Kdf.cpp for the KDBX parameter mapping.
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-vault/argon2
|
|
16
|
+
*/
|
|
17
|
+
import { blake2b } from "./blake2b.js";
|
|
18
|
+
const BLOCK_SIZE = 1024;
|
|
19
|
+
const SYNC_POINTS = 4;
|
|
20
|
+
const ADDRESSES_IN_BLOCK = 128;
|
|
21
|
+
function le32(buf, offset) {
|
|
22
|
+
return (buf[offset] | (buf[offset + 1] << 8) | (buf[offset + 2] << 16) | (buf[offset + 3] << 24)) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
function writeLe32(buf, offset, value) {
|
|
25
|
+
buf.writeUInt32LE(value >>> 0, offset);
|
|
26
|
+
}
|
|
27
|
+
/** 32-bit rotate right. */
|
|
28
|
+
function rotr32(x, n) {
|
|
29
|
+
return ((x >>> n) | (x << (32 - n))) >>> 0;
|
|
30
|
+
}
|
|
31
|
+
/** 64-bit rotate right of a lo/hi pair. */
|
|
32
|
+
function rotr64(lo, hi, n) {
|
|
33
|
+
n &= 63;
|
|
34
|
+
if (n === 0)
|
|
35
|
+
return [lo >>> 0, hi >>> 0];
|
|
36
|
+
if (n === 32)
|
|
37
|
+
return [hi >>> 0, lo >>> 0];
|
|
38
|
+
if (n < 32) {
|
|
39
|
+
return [
|
|
40
|
+
((lo >>> n) | (hi << (32 - n))) >>> 0,
|
|
41
|
+
((hi >>> n) | (lo << (32 - n))) >>> 0,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
const s = n - 32;
|
|
45
|
+
return [
|
|
46
|
+
((hi >>> s) | (lo << (32 - s))) >>> 0,
|
|
47
|
+
((lo >>> s) | (hi << (32 - s))) >>> 0,
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
/** 64-bit add of (a,b) + (c,d), returns lo/hi. */
|
|
51
|
+
function add64(a, b, c, d) {
|
|
52
|
+
const sumLo = (a >>> 0) + (c >>> 0);
|
|
53
|
+
return [sumLo >>> 0, (b + d + Math.floor(sumLo / 0x100000000)) >>> 0];
|
|
54
|
+
}
|
|
55
|
+
/** 64-bit product of two 32-bit words as [lo, hi] (16-bit limbs). */
|
|
56
|
+
function mul64(a, b) {
|
|
57
|
+
const a0 = a & 0xffff, a1 = a >>> 16;
|
|
58
|
+
const b0 = b & 0xffff, b1 = b >>> 16;
|
|
59
|
+
// a*b = P2*2^32 + P1*2^16 + P0, with P0 = a0b0, P1 = a1b0 + a0b1, P2 = a1b1
|
|
60
|
+
const p0 = a0 * b0;
|
|
61
|
+
const p1 = a1 * b0 + a0 * b1;
|
|
62
|
+
const p2 = a1 * b1;
|
|
63
|
+
const s1 = Math.floor(p0 / 0x10000) + p1; // carry into bits 32..47
|
|
64
|
+
const lo = (p0 & 0xffff) | ((s1 & 0xffff) << 16);
|
|
65
|
+
const s2 = Math.floor(s1 / 0x10000) + p2; // carry into bits 48..63
|
|
66
|
+
const hi = (s2 & 0xffff) | ((Math.floor(s2 / 0x10000) & 0xffff) << 16);
|
|
67
|
+
return [lo >>> 0, hi >>> 0];
|
|
68
|
+
}
|
|
69
|
+
/** Low 64 bits of a*b where a,b are 32-bit words. */
|
|
70
|
+
function mulLo64(a, b) {
|
|
71
|
+
return mul64(a, b);
|
|
72
|
+
}
|
|
73
|
+
/** High 32 bits of a*b (a,b 32-bit), i.e. (a*b) >>> 32 exactly. */
|
|
74
|
+
function mulHigh32(a, b) {
|
|
75
|
+
const a0 = a & 0xffff, a1 = a >>> 16;
|
|
76
|
+
const b0 = b & 0xffff, b1 = b >>> 16;
|
|
77
|
+
const p0 = a0 * b0;
|
|
78
|
+
const p1 = a1 * b0 + a0 * b1;
|
|
79
|
+
const p2 = a1 * b1;
|
|
80
|
+
const s1 = Math.floor(p0 / 0x10000) + p1; // bits 16..47 carry
|
|
81
|
+
const s2 = Math.floor(s1 / 0x10000) + p2; // bits 32..63
|
|
82
|
+
return s2 >>> 0;
|
|
83
|
+
}
|
|
84
|
+
/** fBlaMka (Lyra PHC team): x + y + 2*((x&0xffffffff)*(y&0xffffffff)) low 64. */
|
|
85
|
+
function fBlaMka(xLo, xHi, yLo, yHi) {
|
|
86
|
+
const [pLo, pHi] = mulLo64(xLo, yLo);
|
|
87
|
+
// x + y
|
|
88
|
+
const [s1Lo, s1Hi] = add64(xLo, xHi, yLo, yHi);
|
|
89
|
+
// + product
|
|
90
|
+
const [s2Lo, s2Hi] = add64(s1Lo, s1Hi, pLo, pHi);
|
|
91
|
+
// + product again (2× product)
|
|
92
|
+
return add64(s2Lo, s2Hi, pLo, pHi);
|
|
93
|
+
}
|
|
94
|
+
/** H' — variable-length hash (RFC 9106 §3.3). */
|
|
95
|
+
function variableHash(data, length) {
|
|
96
|
+
const prefix = Buffer.alloc(4);
|
|
97
|
+
prefix.writeUInt32LE(length >>> 0, 0);
|
|
98
|
+
const prefixed = Buffer.concat([prefix, data]);
|
|
99
|
+
if (length <= 64)
|
|
100
|
+
return blake2b(prefixed, length);
|
|
101
|
+
const r = Math.ceil(length / 32) - 2;
|
|
102
|
+
const out = Buffer.alloc(length);
|
|
103
|
+
let current = blake2b(prefixed, 64); // V_1
|
|
104
|
+
current.copy(out, 0, 0, 32);
|
|
105
|
+
let position = 32;
|
|
106
|
+
for (let i = 2; i <= r; i++) {
|
|
107
|
+
current = blake2b(current, 64);
|
|
108
|
+
current.copy(out, position, 0, 32);
|
|
109
|
+
position += 32;
|
|
110
|
+
}
|
|
111
|
+
const last = blake2b(current, length - 32 * r);
|
|
112
|
+
last.copy(out, position);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
/** Initial hash H0 (RFC 9106 §3.2): interleaved length-then-data fields. */
|
|
116
|
+
function initialHash(opts) {
|
|
117
|
+
const parts = [];
|
|
118
|
+
const u32 = (v) => {
|
|
119
|
+
const b = Buffer.alloc(4);
|
|
120
|
+
b.writeUInt32LE(v >>> 0, 0);
|
|
121
|
+
return b;
|
|
122
|
+
};
|
|
123
|
+
parts.push(u32(opts.parallelism), u32(opts.hashLength), u32(opts.memoryKiB), u32(opts.iterations), u32(opts.version), u32(opts.type), u32(opts.password.length), opts.password, u32(opts.salt.length), opts.salt, u32(opts.secret?.length ?? 0), ...(opts.secret !== undefined ? [opts.secret] : []), u32(opts.associatedData?.length ?? 0), ...(opts.associatedData !== undefined ? [opts.associatedData] : []));
|
|
124
|
+
return blake2b(Buffer.concat(parts), 64);
|
|
125
|
+
}
|
|
126
|
+
/** G mixing on four 64-bit words (lo/hi pairs at indices 2a..2d+1). */
|
|
127
|
+
function gMix(v, a, b, c, d) {
|
|
128
|
+
// a = fBlaMka(a, b)
|
|
129
|
+
const fa = fBlaMka(v[a], v[a + 1], v[b], v[b + 1]);
|
|
130
|
+
v[a] = fa[0];
|
|
131
|
+
v[a + 1] = fa[1];
|
|
132
|
+
// d = rotr64(d ^ a, 32)
|
|
133
|
+
let lo = v[d] ^ v[a];
|
|
134
|
+
let hi = v[d + 1] ^ v[a + 1];
|
|
135
|
+
let r = rotr64(lo, hi, 32);
|
|
136
|
+
v[d] = r[0];
|
|
137
|
+
v[d + 1] = r[1];
|
|
138
|
+
// c = fBlaMka(c, d)
|
|
139
|
+
const fc = fBlaMka(v[c], v[c + 1], v[d], v[d + 1]);
|
|
140
|
+
v[c] = fc[0];
|
|
141
|
+
v[c + 1] = fc[1];
|
|
142
|
+
// b = rotr64(b ^ c, 24)
|
|
143
|
+
lo = v[b] ^ v[c];
|
|
144
|
+
hi = v[b + 1] ^ v[c + 1];
|
|
145
|
+
r = rotr64(lo, hi, 24);
|
|
146
|
+
v[b] = r[0];
|
|
147
|
+
v[b + 1] = r[1];
|
|
148
|
+
// a = fBlaMka(a, b)
|
|
149
|
+
const fa2 = fBlaMka(v[a], v[a + 1], v[b], v[b + 1]);
|
|
150
|
+
v[a] = fa2[0];
|
|
151
|
+
v[a + 1] = fa2[1];
|
|
152
|
+
// d = rotr64(d ^ a, 16)
|
|
153
|
+
lo = v[d] ^ v[a];
|
|
154
|
+
hi = v[d + 1] ^ v[a + 1];
|
|
155
|
+
r = rotr64(lo, hi, 16);
|
|
156
|
+
v[d] = r[0];
|
|
157
|
+
v[d + 1] = r[1];
|
|
158
|
+
// c = fBlaMka(c, d)
|
|
159
|
+
const fc2 = fBlaMka(v[c], v[c + 1], v[d], v[d + 1]);
|
|
160
|
+
v[c] = fc2[0];
|
|
161
|
+
v[c + 1] = fc2[1];
|
|
162
|
+
// b = rotr64(b ^ c, 63)
|
|
163
|
+
lo = v[b] ^ v[c];
|
|
164
|
+
hi = v[b + 1] ^ v[c + 1];
|
|
165
|
+
r = rotr64(lo, hi, 63);
|
|
166
|
+
v[b] = r[0];
|
|
167
|
+
v[b + 1] = r[1];
|
|
168
|
+
}
|
|
169
|
+
/** One BLAKE2_ROUND_NOMSG on 16 64-bit words (lo/hi entries). */
|
|
170
|
+
function blake2RoundNoMsg(v, base) {
|
|
171
|
+
gMix(v, base + 0, base + 8, base + 16, base + 24);
|
|
172
|
+
gMix(v, base + 2, base + 10, base + 18, base + 26);
|
|
173
|
+
gMix(v, base + 4, base + 12, base + 20, base + 28);
|
|
174
|
+
gMix(v, base + 6, base + 14, base + 22, base + 30);
|
|
175
|
+
gMix(v, base + 0, base + 10, base + 20, base + 30);
|
|
176
|
+
gMix(v, base + 2, base + 12, base + 22, base + 24);
|
|
177
|
+
gMix(v, base + 4, base + 14, base + 16, base + 26);
|
|
178
|
+
gMix(v, base + 6, base + 8, base + 18, base + 28);
|
|
179
|
+
}
|
|
180
|
+
/** One BLAKE2_ROUND_NOMSG over 16 explicit word indices (lo/hi = 2×word). */
|
|
181
|
+
function blake2RoundNoMsgWords(v, w) {
|
|
182
|
+
const e = w.map(x => x * 2);
|
|
183
|
+
gMix(v, e[0], e[4], e[8], e[12]);
|
|
184
|
+
gMix(v, e[1], e[5], e[9], e[13]);
|
|
185
|
+
gMix(v, e[2], e[6], e[10], e[14]);
|
|
186
|
+
gMix(v, e[3], e[7], e[11], e[15]);
|
|
187
|
+
gMix(v, e[0], e[5], e[10], e[15]);
|
|
188
|
+
gMix(v, e[1], e[6], e[11], e[12]);
|
|
189
|
+
gMix(v, e[2], e[7], e[8], e[13]);
|
|
190
|
+
gMix(v, e[3], e[4], e[9], e[14]);
|
|
191
|
+
}
|
|
192
|
+
/** A 1024-byte block viewed as 128 little-endian 64-bit words (lo/hi). */
|
|
193
|
+
class Block {
|
|
194
|
+
v = new Array(256);
|
|
195
|
+
raw;
|
|
196
|
+
constructor(raw) {
|
|
197
|
+
this.raw = raw ?? Buffer.alloc(BLOCK_SIZE);
|
|
198
|
+
this.load();
|
|
199
|
+
}
|
|
200
|
+
load() {
|
|
201
|
+
for (let i = 0; i < 128; i++) {
|
|
202
|
+
this.v[i * 2] = le32(this.raw, i * 8);
|
|
203
|
+
this.v[i * 2 + 1] = le32(this.raw, i * 8 + 4);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
toBuffer() {
|
|
207
|
+
for (let i = 0; i < 128; i++) {
|
|
208
|
+
this.raw.writeUInt32LE(this.v[i * 2] >>> 0, i * 8);
|
|
209
|
+
this.raw.writeUInt32LE(this.v[i * 2 + 1] >>> 0, i * 8 + 4);
|
|
210
|
+
}
|
|
211
|
+
return this.raw;
|
|
212
|
+
}
|
|
213
|
+
clear() {
|
|
214
|
+
this.v.fill(0);
|
|
215
|
+
}
|
|
216
|
+
copyFrom(other) {
|
|
217
|
+
for (let i = 0; i < 256; i++)
|
|
218
|
+
this.v[i] = other.v[i];
|
|
219
|
+
}
|
|
220
|
+
xorAssign(other) {
|
|
221
|
+
for (let i = 0; i < 256; i++)
|
|
222
|
+
this.v[i] = (this.v[i] ^ other.v[i]) >>> 0;
|
|
223
|
+
}
|
|
224
|
+
/** Permutation P: column rounds then row rounds (reference fill_block). */
|
|
225
|
+
permute() {
|
|
226
|
+
for (let i = 0; i < 8; i++)
|
|
227
|
+
blake2RoundNoMsg(this.v, 32 * i);
|
|
228
|
+
for (let i = 0; i < 8; i++) {
|
|
229
|
+
const b = 2 * i;
|
|
230
|
+
blake2RoundNoMsgWords(this.v, [
|
|
231
|
+
b, b + 1, b + 16, b + 17, b + 32, b + 33, b + 48, b + 49,
|
|
232
|
+
b + 64, b + 65, b + 80, b + 81, b + 96, b + 97, b + 112, b + 113,
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Reference fill_block: next = (prev XOR ref) after P, XORed with next if withXor. */
|
|
238
|
+
function fillBlock(prev, ref, next, withXor) {
|
|
239
|
+
const blockR = new Block();
|
|
240
|
+
blockR.copyFrom(ref);
|
|
241
|
+
blockR.xorAssign(prev);
|
|
242
|
+
const blockTmp = new Block();
|
|
243
|
+
blockTmp.copyFrom(blockR);
|
|
244
|
+
if (withXor)
|
|
245
|
+
blockTmp.xorAssign(next);
|
|
246
|
+
blockR.permute();
|
|
247
|
+
// next = blockTmp XOR blockR
|
|
248
|
+
blockTmp.xorAssign(blockR);
|
|
249
|
+
next.copyFrom(blockTmp);
|
|
250
|
+
}
|
|
251
|
+
/** next_addresses: generate the next 128 address words. */
|
|
252
|
+
function nextAddresses(address, input, zero) {
|
|
253
|
+
input.v[12] = (input.v[12] + 1) >>> 0; // v[6] is the 7th 64-bit word
|
|
254
|
+
fillBlock(zero, input, address, false);
|
|
255
|
+
fillBlock(zero, address, address, false);
|
|
256
|
+
}
|
|
257
|
+
/** Reference index_alpha (RFC 9106 §3.4.1.3). */
|
|
258
|
+
function indexAlpha(pass, slice, index, sameLane, segmentLength, laneLength, pseudoRand) {
|
|
259
|
+
let referenceAreaSize;
|
|
260
|
+
if (pass === 0) {
|
|
261
|
+
if (slice === 0) {
|
|
262
|
+
referenceAreaSize = index - 1;
|
|
263
|
+
}
|
|
264
|
+
else if (sameLane) {
|
|
265
|
+
referenceAreaSize = slice * segmentLength + index - 1;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
referenceAreaSize = slice * segmentLength + (index === 0 ? -1 : 0);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else if (sameLane) {
|
|
272
|
+
referenceAreaSize = laneLength - segmentLength + index - 1;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
referenceAreaSize = laneLength - segmentLength + (index === 0 ? -1 : 0);
|
|
276
|
+
}
|
|
277
|
+
let relativePosition = pseudoRand >>> 0;
|
|
278
|
+
relativePosition = mulHigh32(relativePosition, relativePosition);
|
|
279
|
+
relativePosition = referenceAreaSize - 1 - mulHigh32(referenceAreaSize, relativePosition);
|
|
280
|
+
let startPosition = 0;
|
|
281
|
+
if (pass !== 0) {
|
|
282
|
+
startPosition = slice === SYNC_POINTS - 1 ? 0 : (slice + 1) * segmentLength;
|
|
283
|
+
}
|
|
284
|
+
return (startPosition + relativePosition) % laneLength;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Argon2 (RFC 9106). `type` 0 = Argon2d, 2 = Argon2id. Returns the tag.
|
|
288
|
+
*/
|
|
289
|
+
export function argon2(opts) {
|
|
290
|
+
const lanes = opts.parallelism ?? 1;
|
|
291
|
+
const passes = opts.iterations ?? 3;
|
|
292
|
+
const memoryKiB = opts.memoryKiB ?? 32;
|
|
293
|
+
const hashLen = opts.hashLength ?? 32;
|
|
294
|
+
const type = opts.type ?? 0;
|
|
295
|
+
const version = opts.version ?? 0x13;
|
|
296
|
+
if (lanes < 1 || passes < 1)
|
|
297
|
+
throw new Error('argon2: parallelism and iterations must be >= 1');
|
|
298
|
+
if (memoryKiB < 8 * lanes)
|
|
299
|
+
throw new Error(`argon2: memory (${memoryKiB} KiB) too small for ${lanes} lanes (min ${8 * lanes})`);
|
|
300
|
+
const segmentLength = Math.floor(memoryKiB / (lanes * SYNC_POINTS));
|
|
301
|
+
const laneLength = segmentLength * SYNC_POINTS;
|
|
302
|
+
const memoryBlocks = laneLength * lanes;
|
|
303
|
+
const h0 = initialHash({
|
|
304
|
+
password: opts.password, salt: opts.salt, parallelism: lanes, iterations: passes,
|
|
305
|
+
memoryKiB, hashLength: hashLen, type, version,
|
|
306
|
+
...(opts.secret !== undefined ? { secret: opts.secret } : {}),
|
|
307
|
+
...(opts.associatedData !== undefined ? { associatedData: opts.associatedData } : {}),
|
|
308
|
+
});
|
|
309
|
+
// Memory blocks (all allocated), initialized with H'(H0 || le32(idx) || le32(lane)).
|
|
310
|
+
const blocks = [];
|
|
311
|
+
for (let i = 0; i < memoryBlocks; i++)
|
|
312
|
+
blocks.push(new Block());
|
|
313
|
+
for (let lane = 0; lane < lanes; lane++) {
|
|
314
|
+
for (let b = 0; b < 2; b++) {
|
|
315
|
+
const input = Buffer.alloc(72);
|
|
316
|
+
h0.copy(input, 0, 0, 64);
|
|
317
|
+
writeLe32(input, 64, b);
|
|
318
|
+
writeLe32(input, 68, lane);
|
|
319
|
+
const blk = blocks[lane * laneLength + b];
|
|
320
|
+
blk.clear();
|
|
321
|
+
const h = variableHash(input, BLOCK_SIZE);
|
|
322
|
+
for (let i = 0; i < 128; i++) {
|
|
323
|
+
blk.v[i * 2] = le32(h, i * 8);
|
|
324
|
+
blk.v[i * 2 + 1] = le32(h, i * 8 + 4);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const zero = new Block();
|
|
329
|
+
zero.clear();
|
|
330
|
+
const input = new Block();
|
|
331
|
+
input.clear();
|
|
332
|
+
const address = new Block();
|
|
333
|
+
address.clear();
|
|
334
|
+
const dataIndependentAddressing = (pass, slice) => type === 1 || (type === 2 && pass === 0 && slice < SYNC_POINTS / 2);
|
|
335
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
336
|
+
for (let slice = 0; slice < SYNC_POINTS; slice++) {
|
|
337
|
+
for (let lane = 0; lane < lanes; lane++) {
|
|
338
|
+
const dataIndependent = dataIndependentAddressing(pass, slice);
|
|
339
|
+
if (dataIndependent) {
|
|
340
|
+
// input_block.v[0..5] = pass, lane, slice, memory_blocks, passes, type
|
|
341
|
+
input.clear();
|
|
342
|
+
input.v[0] = pass >>> 0;
|
|
343
|
+
input.v[2] = lane >>> 0;
|
|
344
|
+
input.v[4] = slice >>> 0;
|
|
345
|
+
input.v[6] = memoryBlocks >>> 0;
|
|
346
|
+
input.v[8] = passes >>> 0;
|
|
347
|
+
input.v[10] = type >>> 0;
|
|
348
|
+
}
|
|
349
|
+
let startingIndex = 0;
|
|
350
|
+
if (pass === 0 && slice === 0) {
|
|
351
|
+
startingIndex = 2;
|
|
352
|
+
if (dataIndependent)
|
|
353
|
+
nextAddresses(address, input, zero);
|
|
354
|
+
}
|
|
355
|
+
// current/previous offsets within the lane (wrap at lane boundary)
|
|
356
|
+
for (let i = startingIndex; i < segmentLength; i++) {
|
|
357
|
+
const currPos = lane * laneLength + slice * segmentLength + i;
|
|
358
|
+
let prevPos;
|
|
359
|
+
if (currPos % laneLength === 0)
|
|
360
|
+
prevPos = currPos + laneLength - 1;
|
|
361
|
+
else
|
|
362
|
+
prevPos = currPos - 1;
|
|
363
|
+
if (currPos % laneLength === 1)
|
|
364
|
+
prevPos = currPos - 1;
|
|
365
|
+
// pseudo-random 64-bit word from previous block or address block
|
|
366
|
+
let pseudoLo;
|
|
367
|
+
let pseudoHi;
|
|
368
|
+
if (dataIndependent) {
|
|
369
|
+
if (i % ADDRESSES_IN_BLOCK === 0)
|
|
370
|
+
nextAddresses(address, input, zero);
|
|
371
|
+
const w = (i % ADDRESSES_IN_BLOCK) * 2;
|
|
372
|
+
pseudoLo = address.v[w];
|
|
373
|
+
pseudoHi = address.v[w + 1];
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
pseudoLo = blocks[prevPos].v[0];
|
|
377
|
+
pseudoHi = blocks[prevPos].v[1];
|
|
378
|
+
}
|
|
379
|
+
const refLane = pass === 0 && slice === 0
|
|
380
|
+
? lane
|
|
381
|
+
: (pseudoHi % lanes);
|
|
382
|
+
const sameLane = refLane === lane;
|
|
383
|
+
const refIndex = indexAlpha(pass, slice, i, sameLane, segmentLength, laneLength, pseudoLo);
|
|
384
|
+
const refPos = refLane * laneLength + refIndex;
|
|
385
|
+
if (blocks[prevPos] === undefined || blocks[refPos] === undefined || blocks[currPos] === undefined) {
|
|
386
|
+
throw new Error(`argon2: OOB pass=${pass} slice=${slice} lane=${lane} i=${i} curr=${currPos} prev=${prevPos} ref=${refPos} (mem=${memoryBlocks})`);
|
|
387
|
+
}
|
|
388
|
+
const refBlock = blocks[refPos];
|
|
389
|
+
const currBlock = blocks[currPos];
|
|
390
|
+
fillBlock(blocks[prevPos], refBlock, currBlock, pass > 0);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Final block: XOR of each lane's last block, then H'.
|
|
396
|
+
const final = new Block();
|
|
397
|
+
final.clear();
|
|
398
|
+
for (let lane = 0; lane < lanes; lane++) {
|
|
399
|
+
final.xorAssign(blocks[lane * laneLength + laneLength - 1]);
|
|
400
|
+
}
|
|
401
|
+
return variableHash(final.toBuffer(), hashLen);
|
|
402
|
+
}
|
package/lib/blake2b.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blake2b hash (RFC 7693) for dsh-vault — used by the Argon2 KDF (RFC 9106).
|
|
3
|
+
*
|
|
4
|
+
* Pure-JS implementation. 64-bit words are represented as lo/hi 32-bit halves
|
|
5
|
+
* to avoid BigInt overhead. Supports digest lengths 1–64 and optional key.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-vault/blake2b
|
|
8
|
+
*/
|
|
9
|
+
const IV = [
|
|
10
|
+
0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, // lo, hi
|
|
11
|
+
0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a,
|
|
12
|
+
0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c,
|
|
13
|
+
0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19,
|
|
14
|
+
];
|
|
15
|
+
const SIGMA = [
|
|
16
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
17
|
+
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
|
18
|
+
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
|
19
|
+
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
|
20
|
+
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
|
21
|
+
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
|
22
|
+
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
|
23
|
+
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
|
24
|
+
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
|
25
|
+
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
|
|
26
|
+
];
|
|
27
|
+
/** 64-bit rotate right of a lo/hi word pair. */
|
|
28
|
+
function rotr64(lo, hi, n) {
|
|
29
|
+
n &= 63;
|
|
30
|
+
if (n === 0)
|
|
31
|
+
return [lo >>> 0, hi >>> 0];
|
|
32
|
+
if (n === 32)
|
|
33
|
+
return [hi >>> 0, lo >>> 0];
|
|
34
|
+
if (n < 32) {
|
|
35
|
+
return [
|
|
36
|
+
((lo >>> n) | (hi << (32 - n))) >>> 0,
|
|
37
|
+
((hi >>> n) | (lo << (32 - n))) >>> 0,
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
const shift = n - 32;
|
|
41
|
+
return [
|
|
42
|
+
((hi >>> shift) | (lo << (32 - shift))) >>> 0,
|
|
43
|
+
((lo >>> shift) | (hi << (32 - shift))) >>> 0,
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
/** 64-bit add of (a,b) + (c,d), returns lo/hi. */
|
|
47
|
+
function add64(a, b, c, d) {
|
|
48
|
+
const sumLo = (a >>> 0) + (c >>> 0);
|
|
49
|
+
const carry = Math.floor(sumLo / 0x100000000);
|
|
50
|
+
return [sumLo >>> 0, (b + d + carry) >>> 0];
|
|
51
|
+
}
|
|
52
|
+
/** G mixing function operating on 64-bit words in `v` (lo at even index). */
|
|
53
|
+
function g(v, a, b, c, d, xLo, xHi, yLo, yHi) {
|
|
54
|
+
// a = a + b + x
|
|
55
|
+
const a1 = add64(v[a], v[a + 1], v[b], v[b + 1]);
|
|
56
|
+
const a2 = add64(a1[0], a1[1], xLo, xHi);
|
|
57
|
+
v[a] = a2[0];
|
|
58
|
+
v[a + 1] = a2[1];
|
|
59
|
+
// d = rotr64(d ^ a, 32)
|
|
60
|
+
let lo = v[d] ^ v[a];
|
|
61
|
+
let hi = v[d + 1] ^ v[a + 1];
|
|
62
|
+
let r = rotr64(lo, hi, 32);
|
|
63
|
+
v[d] = r[0];
|
|
64
|
+
v[d + 1] = r[1];
|
|
65
|
+
// c = c + d
|
|
66
|
+
const c1 = add64(v[c], v[c + 1], v[d], v[d + 1]);
|
|
67
|
+
v[c] = c1[0];
|
|
68
|
+
v[c + 1] = c1[1];
|
|
69
|
+
// b = rotr64(b ^ c, 24)
|
|
70
|
+
lo = v[b] ^ v[c];
|
|
71
|
+
hi = v[b + 1] ^ v[c + 1];
|
|
72
|
+
r = rotr64(lo, hi, 24);
|
|
73
|
+
v[b] = r[0];
|
|
74
|
+
v[b + 1] = r[1];
|
|
75
|
+
// a = a + b + y
|
|
76
|
+
const a3 = add64(v[a], v[a + 1], v[b], v[b + 1]);
|
|
77
|
+
const a4 = add64(a3[0], a3[1], yLo, yHi);
|
|
78
|
+
v[a] = a4[0];
|
|
79
|
+
v[a + 1] = a4[1];
|
|
80
|
+
// d = rotr64(d ^ a, 16)
|
|
81
|
+
lo = v[d] ^ v[a];
|
|
82
|
+
hi = v[d + 1] ^ v[a + 1];
|
|
83
|
+
r = rotr64(lo, hi, 16);
|
|
84
|
+
v[d] = r[0];
|
|
85
|
+
v[d + 1] = r[1];
|
|
86
|
+
// c = c + d
|
|
87
|
+
const c2 = add64(v[c], v[c + 1], v[d], v[d + 1]);
|
|
88
|
+
v[c] = c2[0];
|
|
89
|
+
v[c + 1] = c2[1];
|
|
90
|
+
// b = rotr64(b ^ c, 63)
|
|
91
|
+
lo = v[b] ^ v[c];
|
|
92
|
+
hi = v[b + 1] ^ v[c + 1];
|
|
93
|
+
r = rotr64(lo, hi, 63);
|
|
94
|
+
v[b] = r[0];
|
|
95
|
+
v[b + 1] = r[1];
|
|
96
|
+
}
|
|
97
|
+
/** Create a Blake2b state (supports keying and custom digest length). */
|
|
98
|
+
export function blake2bInit(outlen, key) {
|
|
99
|
+
if (outlen < 1 || outlen > 64)
|
|
100
|
+
throw new Error('blake2b: digest length must be 1–64');
|
|
101
|
+
// Parameter block (little-endian 64-bit): digest_length | key_length<<8 |
|
|
102
|
+
// fanout<<16 | depth<<24; the rest of the block is zero.
|
|
103
|
+
const paramLo = (outlen & 0xff) | ((key?.length ?? 0) << 8) | 0x01010000;
|
|
104
|
+
const h = [...IV];
|
|
105
|
+
h[0] = (h[0] ^ paramLo) >>> 0;
|
|
106
|
+
const st = { h, t0: 0, t1: 0, buf: Buffer.alloc(128), buflen: 0, outlen };
|
|
107
|
+
if (key !== undefined && key.length > 0) {
|
|
108
|
+
if (key.length > 128)
|
|
109
|
+
throw new Error('blake2b: key too long');
|
|
110
|
+
// RFC 7693 §3.4: the key is padded to the block size and processed as a
|
|
111
|
+
// block. Buffer it (buflen=128) so an empty message compresses it as the
|
|
112
|
+
// final block, while any following message flushes it first.
|
|
113
|
+
const block = Buffer.alloc(128);
|
|
114
|
+
key.copy(block);
|
|
115
|
+
block.copy(st.buf);
|
|
116
|
+
st.buflen = 128;
|
|
117
|
+
}
|
|
118
|
+
return st;
|
|
119
|
+
}
|
|
120
|
+
/** Incremental update with `data`. */
|
|
121
|
+
export function blake2bUpdate(st, data) {
|
|
122
|
+
if (data.length === 0)
|
|
123
|
+
return;
|
|
124
|
+
let pos = 0;
|
|
125
|
+
// A full buffered block (e.g. the padded key block) must be flushed first.
|
|
126
|
+
if (st.buflen === 128) {
|
|
127
|
+
incrementCounter(st, 128);
|
|
128
|
+
compress(st, st.buf, false);
|
|
129
|
+
st.buflen = 0;
|
|
130
|
+
}
|
|
131
|
+
if (st.buflen > 0) {
|
|
132
|
+
const take = Math.min(128 - st.buflen, data.length);
|
|
133
|
+
data.copy(st.buf, st.buflen, 0, take);
|
|
134
|
+
st.buflen += take;
|
|
135
|
+
pos += take;
|
|
136
|
+
if (st.buflen === 128) {
|
|
137
|
+
incrementCounter(st, 128);
|
|
138
|
+
compress(st, st.buf, false);
|
|
139
|
+
st.buflen = 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
while (pos + 128 <= data.length) {
|
|
143
|
+
incrementCounter(st, 128);
|
|
144
|
+
compress(st, data.subarray(pos, pos + 128), false);
|
|
145
|
+
pos += 128;
|
|
146
|
+
}
|
|
147
|
+
if (pos < data.length) {
|
|
148
|
+
data.copy(st.buf, 0, pos);
|
|
149
|
+
st.buflen = data.length - pos;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Finalize and return the digest. */
|
|
153
|
+
export function blake2bFinal(st) {
|
|
154
|
+
incrementCounter(st, st.buflen);
|
|
155
|
+
const block = Buffer.alloc(128);
|
|
156
|
+
st.buf.copy(block, 0, 0, st.buflen);
|
|
157
|
+
compress(st, block, true);
|
|
158
|
+
const out = Buffer.alloc(st.outlen);
|
|
159
|
+
for (let i = 0; i < st.outlen; i++) {
|
|
160
|
+
// The state is 16 words of 64 bits stored as [lo, hi]; little-endian
|
|
161
|
+
// byte order is lo's 4 bytes then hi's 4 bytes per word.
|
|
162
|
+
const word = i >>> 3;
|
|
163
|
+
const isLo = (i & 4) === 0;
|
|
164
|
+
const shift = (i & 3) * 8;
|
|
165
|
+
out[i] = ((isLo ? st.h[word * 2] : st.h[word * 2 + 1]) >>> shift) & 0xff;
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
/** One-shot Blake2b digest. */
|
|
170
|
+
export function blake2b(data, outlen = 64, key) {
|
|
171
|
+
const st = blake2bInit(outlen, key);
|
|
172
|
+
blake2bUpdate(st, data);
|
|
173
|
+
return blake2bFinal(st);
|
|
174
|
+
}
|
|
175
|
+
function incrementCounter(st, amount) {
|
|
176
|
+
st.t0 = (st.t0 + amount) >>> 0;
|
|
177
|
+
if (st.t0 < amount)
|
|
178
|
+
st.t1 = (st.t1 + 1) >>> 0;
|
|
179
|
+
}
|
|
180
|
+
/** Compress one 128-byte block (mix the state). */
|
|
181
|
+
function compress(st, block, last) {
|
|
182
|
+
const v = new Array(32);
|
|
183
|
+
for (let i = 0; i < 16; i++) {
|
|
184
|
+
v[i] = st.h[i];
|
|
185
|
+
v[16 + i] = IV[i];
|
|
186
|
+
}
|
|
187
|
+
v[24] = (v[24] ^ st.t0) >>> 0;
|
|
188
|
+
v[25] = (v[25] ^ st.t1) >>> 0;
|
|
189
|
+
if (last) {
|
|
190
|
+
v[28] = (v[28] ^ 0xffffffff) >>> 0;
|
|
191
|
+
v[29] = (v[29] ^ 0xffffffff) >>> 0;
|
|
192
|
+
}
|
|
193
|
+
const m = new Array(32);
|
|
194
|
+
for (let i = 0; i < 16; i++) {
|
|
195
|
+
m[i * 2] = block.readUInt32LE(i * 8);
|
|
196
|
+
m[i * 2 + 1] = block.readUInt32LE(i * 8 + 4);
|
|
197
|
+
}
|
|
198
|
+
for (let round = 0; round < 12; round++) {
|
|
199
|
+
const s = SIGMA[round % 10];
|
|
200
|
+
g(v, 0, 8, 16, 24, m[s[0] * 2], m[s[0] * 2 + 1], m[s[1] * 2], m[s[1] * 2 + 1]);
|
|
201
|
+
g(v, 2, 10, 18, 26, m[s[2] * 2], m[s[2] * 2 + 1], m[s[3] * 2], m[s[3] * 2 + 1]);
|
|
202
|
+
g(v, 4, 12, 20, 28, m[s[4] * 2], m[s[4] * 2 + 1], m[s[5] * 2], m[s[5] * 2 + 1]);
|
|
203
|
+
g(v, 6, 14, 22, 30, m[s[6] * 2], m[s[6] * 2 + 1], m[s[7] * 2], m[s[7] * 2 + 1]);
|
|
204
|
+
g(v, 0, 10, 20, 30, m[s[8] * 2], m[s[8] * 2 + 1], m[s[9] * 2], m[s[9] * 2 + 1]);
|
|
205
|
+
g(v, 2, 12, 22, 24, m[s[10] * 2], m[s[10] * 2 + 1], m[s[11] * 2], m[s[11] * 2 + 1]);
|
|
206
|
+
g(v, 4, 14, 16, 26, m[s[12] * 2], m[s[12] * 2 + 1], m[s[13] * 2], m[s[13] * 2 + 1]);
|
|
207
|
+
g(v, 6, 8, 18, 28, m[s[14] * 2], m[s[14] * 2 + 1], m[s[15] * 2], m[s[15] * 2 + 1]);
|
|
208
|
+
}
|
|
209
|
+
for (let i = 0; i < 16; i++) {
|
|
210
|
+
st.h[i] = (st.h[i] ^ v[i] ^ v[16 + i]) >>> 0;
|
|
211
|
+
}
|
|
212
|
+
}
|
package/lib/chacha20.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChaCha20 stream cipher (IETF variant, 96-bit nonce) for dsh-vault.
|
|
3
|
+
*
|
|
4
|
+
* Node's crypto module has no ChaCha20 primitive, so this implements the
|
|
5
|
+
* standard 20-round construction needed to decrypt KeePass KDBX protected
|
|
6
|
+
* fields (inner random stream id 2) and other plaintext streams.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-vault/chacha20
|
|
9
|
+
*/
|
|
10
|
+
const ROUNDS = 20;
|
|
11
|
+
const STATE_SIZE = 16;
|
|
12
|
+
function rotl(x, n) {
|
|
13
|
+
return ((x << n) | (x >>> (32 - n))) >>> 0;
|
|
14
|
+
}
|
|
15
|
+
function quarterRound(state, a, b, c, d) {
|
|
16
|
+
state[a] = (state[a] + state[b]) >>> 0;
|
|
17
|
+
state[d] = rotl(state[d] ^ state[a], 16);
|
|
18
|
+
state[c] = (state[c] + state[d]) >>> 0;
|
|
19
|
+
state[b] = rotl(state[b] ^ state[c], 12);
|
|
20
|
+
state[a] = (state[a] + state[b]) >>> 0;
|
|
21
|
+
state[d] = rotl(state[d] ^ state[a], 8);
|
|
22
|
+
state[c] = (state[c] + state[d]) >>> 0;
|
|
23
|
+
state[b] = rotl(state[b] ^ state[c], 7);
|
|
24
|
+
}
|
|
25
|
+
function littleEndian(buf, offset) {
|
|
26
|
+
return (buf[offset] |
|
|
27
|
+
(buf[offset + 1] << 8) |
|
|
28
|
+
(buf[offset + 2] << 16) |
|
|
29
|
+
(buf[offset + 3] << 24)) >>> 0;
|
|
30
|
+
}
|
|
31
|
+
function toBytes(word, out, offset) {
|
|
32
|
+
out[offset] = word & 0xff;
|
|
33
|
+
out[offset + 1] = (word >>> 8) & 0xff;
|
|
34
|
+
out[offset + 2] = (word >>> 16) & 0xff;
|
|
35
|
+
out[offset + 3] = (word >>> 24) & 0xff;
|
|
36
|
+
}
|
|
37
|
+
const CONSTANTS = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
|
|
38
|
+
/** XOR `input` with the ChaCha20 keystream. `key` is 32 bytes, `nonce` 12 bytes. */
|
|
39
|
+
export function chacha20Xor(input, key, nonce, counter = 0) {
|
|
40
|
+
if (key.length !== 32)
|
|
41
|
+
throw new Error('chacha20: key must be 32 bytes');
|
|
42
|
+
if (nonce.length !== 12)
|
|
43
|
+
throw new Error('chacha20: nonce must be 12 bytes');
|
|
44
|
+
const out = Buffer.alloc(input.length);
|
|
45
|
+
const keyWords = [
|
|
46
|
+
littleEndian(key, 0), littleEndian(key, 4), littleEndian(key, 8), littleEndian(key, 12),
|
|
47
|
+
littleEndian(key, 16), littleEndian(key, 20), littleEndian(key, 24), littleEndian(key, 28),
|
|
48
|
+
];
|
|
49
|
+
const nonceWords = [
|
|
50
|
+
littleEndian(nonce, 0), littleEndian(nonce, 4), littleEndian(nonce, 8),
|
|
51
|
+
];
|
|
52
|
+
const block = Buffer.alloc(64);
|
|
53
|
+
let pos = 0;
|
|
54
|
+
let blockCounter = counter;
|
|
55
|
+
while (pos < input.length) {
|
|
56
|
+
const state = [...CONSTANTS, ...keyWords, blockCounter >>> 0, ...nonceWords];
|
|
57
|
+
const working = [...state];
|
|
58
|
+
for (let i = 0; i < ROUNDS / 2; i++) {
|
|
59
|
+
quarterRound(working, 0, 4, 8, 12);
|
|
60
|
+
quarterRound(working, 1, 5, 9, 13);
|
|
61
|
+
quarterRound(working, 2, 6, 10, 14);
|
|
62
|
+
quarterRound(working, 3, 7, 11, 15);
|
|
63
|
+
quarterRound(working, 0, 5, 10, 15);
|
|
64
|
+
quarterRound(working, 1, 6, 11, 12);
|
|
65
|
+
quarterRound(working, 2, 7, 8, 13);
|
|
66
|
+
quarterRound(working, 3, 4, 9, 14);
|
|
67
|
+
}
|
|
68
|
+
for (let i = 0; i < STATE_SIZE; i++) {
|
|
69
|
+
toBytes((working[i] + state[i]) >>> 0, block, i * 4);
|
|
70
|
+
}
|
|
71
|
+
const chunk = input.length - pos;
|
|
72
|
+
const take = Math.min(64, chunk);
|
|
73
|
+
for (let i = 0; i < take; i++)
|
|
74
|
+
out[pos + i] = input[pos + i] ^ block[i];
|
|
75
|
+
pos += take;
|
|
76
|
+
blockCounter = (blockCounter + 1) >>> 0;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|