forked from ranisalt/node-argon2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
argon2.js
121 lines (102 loc) · 2.87 KB
/
argon2.js
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
"use strict";
const assert = require("assert");
const { randomBytes, timingSafeEqual } = require("crypto");
const path = require("path");
const { promisify } = require("util");
const binary = require("@mapbox/node-pre-gyp");
const bindingPath = binary.find(path.resolve(__dirname, "./package.json"));
const { hash: _hash } = require(bindingPath);
const { deserialize, serialize } = require("@phc/format");
const types = Object.freeze({ argon2d: 0, argon2i: 1, argon2id: 2 });
const defaults = Object.freeze({
hashLength: 32,
saltLength: 16,
timeCost: 3,
memoryCost: 1 << 16,
parallelism: 4,
type: types.argon2id,
version: 0x13,
});
const limits = Object.freeze({
hashLength: { min: 4, max: 2 ** 32 - 1 },
memoryCost: { min: 1 << 10, max: 2 ** 32 - 1 },
timeCost: { min: 2, max: 2 ** 32 - 1 },
parallelism: { min: 1, max: 2 ** 24 - 1 },
});
const names = Object.freeze({
[types.argon2d]: "argon2d",
[types.argon2i]: "argon2i",
[types.argon2id]: "argon2id",
});
const bindingsHash = promisify(_hash);
const generateSalt = promisify(randomBytes);
const assertLimits =
(options) =>
([key, { max, min }]) => {
const value = options[key];
assert(
min <= value && value <= max,
`Invalid ${key}, must be between ${min} and ${max}.`
);
};
const hash = async (plain, { raw, salt, ...options } = {}) => {
options = { ...defaults, ...options };
Object.entries(limits).forEach(assertLimits(options));
salt = salt || (await generateSalt(options.saltLength));
const hash = await bindingsHash(Buffer.from(plain), salt, options);
if (raw) {
return hash;
}
const {
type,
version,
memoryCost: m,
timeCost: t,
parallelism: p,
associatedData: data,
} = options;
return serialize({
id: names[type],
version,
params: { m, t, p, ...(data ? { data } : {}) },
salt,
hash,
});
};
const needsRehash = (digest, options) => {
const { memoryCost, timeCost, version } = { ...defaults, ...options };
const {
version: v,
params: { m, t },
} = deserialize(digest);
return +v !== +version || +m !== +memoryCost || +t !== +timeCost;
};
const verify = async (digest, plain, options) => {
const obj = deserialize(digest);
// Only these have the "params" key, so if the password was encoded
// using any other method, the destructuring throws an error
if (!(obj.id in types)) {
return false;
}
const {
id,
version = 0x10,
params: { m, t, p, data },
salt,
hash,
} = obj;
return timingSafeEqual(
await bindingsHash(Buffer.from(plain), salt, {
...options,
type: types[id],
version: +version,
hashLength: hash.length,
memoryCost: +m,
timeCost: +t,
parallelism: +p,
...(data ? { associatedData: Buffer.from(data, "base64") } : {}),
}),
hash
);
};
module.exports = { defaults, limits, hash, needsRehash, verify, ...types };