-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
mod.ts
149 lines (134 loc) · 4.36 KB
/
mod.ts
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import * as wasm from "./wasm/mod.ts";
function bufferSourceArrayBuffer(data: BufferSource) {
if (ArrayBuffer.isView(data)) {
return data.buffer;
} else if (data instanceof ArrayBuffer) {
return data;
}
throw new TypeError(
`Could extract ArrayBuffer from alleged BufferSource type. Got ${data} instead.`,
);
}
/**
* Transfers an {@link ArrayBufferLike} to wasm, automatically allocating it in memory.
*
* Remember to unallocate the transfered buffer with {@link wasm.dealloc}
*/
function transfer(buffer: BufferSource): [number, number] {
const length = buffer.byteLength;
const pointer = wasm.alloc(length);
new Uint8Array(wasm.memory.buffer, pointer, length).set(
new Uint8Array(bufferSourceArrayBuffer(buffer)),
);
return [pointer, length];
}
function maybeTransfer(buffer?: BufferSource): [number, number] {
if (buffer != null) {
return transfer(buffer);
}
return [0, 0];
}
/**
* The three different Argon2 algorithm variants as described by [wikipedia](https://en.wikipedia.org/wiki/Argon2):
*
* - **Argon2d**: Argon2d maximizes resistance to GPU cracking attacks. It accesses the memory array in a password dependent order, which reduces the possibility of time–memory trade-off (TMTO) attacks, but introduces possible side-channel attacks.
* - **Argon2i**: Argon2i is optimized to resist side-channel attacks. It accesses the memory array in a password independent order.
* - **Argon2id**: (default) Argon2id is a hybrid version. It follows the Argon2i approach for the first half pass over memory and the Argon2d approach for subsequent passes. RFC 9106 recommends using Argon2id if you do not know the difference between the types or you consider side-channel attacks to be a viable threat.
*/
export type Argon2Algorithm = "Argon2d" | "Argon2i" | "Argon2id";
/**
* The two different versions of the Argon2 algorithm:
*
* - **0x10**: Version 16, performs overwrites internally.
* - **0x13** (default): Version 19, performs XOR internally.
*/
export type Argon2Version = 0x10 | 0x13;
export type Argon2Params = {
algorithm: Argon2Algorithm;
version: Argon2Version;
secret?: ArrayBufferLike;
/**
* The length of the output hash.
*
* @default 32
*/
outputLength?: number;
/**
* Memory size in 1 KiB blocks. Between 1 and (2^32)-1.
*
* When {@link Argon2Params.algorithm} is Argon2i the default is changed to 12288 as per OWASP recommendations.
*
* @default 19456
*/
mCost?: number;
/**
* Number of iterations. Between 1 and (2^32)-1.
*
* When {@link Argon2Params.algorithm} is Argon2i the default is changed to 3 as per OWASP recommendations.
*
* @default 2
*/
tCost?: number;
/**
* Degree of parallelism. Between 1 and 255.
*
* @default 1
*/
pCost?: number;
};
const argon2AlgorithmEnum: Record<Lowercase<Argon2Algorithm>, number> = {
"argon2d": 0,
"argon2i": 1,
"argon2id": 2,
};
/**
* Computes the Argon2 hash for the password, salt and parameters.
*/
export function hash(
password: BufferSource,
salt: BufferSource,
params?: Argon2Params,
): ArrayBuffer {
params ??= {
algorithm: "Argon2id",
version: 0x13,
};
params.outputLength ??= 32;
// These defaults come from https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
params.mCost ??= params.algorithm === "Argon2i" ? 12288 : 19456;
params.tCost ??= params.algorithm === "Argon2i" ? 3 : 2;
params.pCost ??= 1;
const [passwordPtr, passwordLen] = transfer(password);
const [saltPtr, saltLen] = transfer(salt);
const [secretPtr, secretLen] = maybeTransfer(params?.secret);
const outputPtr = wasm.alloc(params.outputLength);
wasm.hash(
passwordPtr,
passwordLen,
saltPtr,
saltLen,
secretPtr,
secretLen,
outputPtr,
params.outputLength,
argon2AlgorithmEnum[
params.algorithm.toLowerCase() as Lowercase<Argon2Algorithm>
],
params.version,
params.mCost,
params.tCost,
params.pCost,
);
wasm.dealloc(passwordPtr, passwordLen);
wasm.dealloc(saltPtr, saltLen);
if (secretPtr !== 0) {
wasm.dealloc(secretPtr, secretLen);
}
const output = new ArrayBuffer(params.outputLength);
// Copy output from wasm memory into js
new Uint8Array(output).set(
new Uint8Array(wasm.memory.buffer, outputPtr, params.outputLength),
);
wasm.dealloc(outputPtr, params.outputLength);
return output;
}