@gjsify/crypto 0.3.13 → 0.3.14
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/esm/asn1.js +576 -450
- package/lib/esm/bigint-math.js +37 -28
- package/lib/esm/cipher.js +1252 -1229
- package/lib/esm/constants.js +12 -13
- package/lib/esm/crypto-utils.js +54 -36
- package/lib/esm/dh.js +408 -368
- package/lib/esm/ecdh.js +403 -321
- package/lib/esm/ecdsa.js +138 -111
- package/lib/esm/hash.js +100 -89
- package/lib/esm/hkdf.js +65 -47
- package/lib/esm/hmac.js +95 -90
- package/lib/esm/index.js +75 -148
- package/lib/esm/key-object.js +348 -307
- package/lib/esm/mgf1.js +30 -24
- package/lib/esm/pbkdf2.js +66 -59
- package/lib/esm/public-encrypt.js +203 -156
- package/lib/esm/random.js +137 -124
- package/lib/esm/rsa-oaep.js +94 -87
- package/lib/esm/rsa-pss.js +95 -88
- package/lib/esm/scrypt.js +116 -115
- package/lib/esm/sign.js +267 -237
- package/lib/esm/timing-safe-equal.js +16 -11
- package/lib/esm/x509.js +215 -206
- package/package.json +7 -7
package/lib/esm/ecdsa.js
CHANGED
|
@@ -1,125 +1,152 @@
|
|
|
1
|
+
import { bigIntToBytes, bytesToBigInt } from "./bigint-math.js";
|
|
2
|
+
import { CURVES, CURVE_ALIASES, mod, modInverse, pointAdd, scalarMul } from "./ecdh.js";
|
|
1
3
|
import { Hash } from "./hash.js";
|
|
2
4
|
import { Hmac } from "./hmac.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
modInverse,
|
|
6
|
-
scalarMul,
|
|
7
|
-
pointAdd,
|
|
8
|
-
CURVES,
|
|
9
|
-
CURVE_ALIASES
|
|
10
|
-
} from "./ecdh.js";
|
|
11
|
-
import { bigIntToBytes as bigintToBytes, bytesToBigInt as bytesToBigint } from "./bigint-math.js";
|
|
5
|
+
|
|
6
|
+
//#region src/ecdsa.ts
|
|
12
7
|
function getCurve(curveName) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const alias = CURVE_ALIASES[curveName.toLowerCase()];
|
|
9
|
+
if (!alias) throw new Error(`Unsupported curve: ${curveName}`);
|
|
10
|
+
return CURVES[alias];
|
|
16
11
|
}
|
|
12
|
+
/** Truncate hash to curve order bit length (FIPS 186-4 Section 6.4) */
|
|
17
13
|
function truncateHash(hash, curve) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
const orderBits = curve.n.toString(2).length;
|
|
15
|
+
let e = bytesToBigInt(hash);
|
|
16
|
+
const hashBits = hash.length * 8;
|
|
17
|
+
if (hashBits > orderBits) {
|
|
18
|
+
e >>= BigInt(hashBits - orderBits);
|
|
19
|
+
}
|
|
20
|
+
return e;
|
|
25
21
|
}
|
|
26
22
|
function hmacDigest(algo, key, data) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const hmac = new Hmac(algo, key);
|
|
24
|
+
hmac.update(data);
|
|
25
|
+
return new Uint8Array(hmac.digest());
|
|
30
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Generate deterministic k per RFC 6979 Section 3.2.
|
|
29
|
+
* Uses HMAC-DRBG seeded with private key and message hash.
|
|
30
|
+
*/
|
|
31
31
|
function rfc6979(hashAlgo, privKey, msgHash, curve) {
|
|
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
|
-
|
|
32
|
+
const qLen = curve.byteLength;
|
|
33
|
+
const hLen = msgHash.length;
|
|
34
|
+
let V = new Uint8Array(hLen).fill(1);
|
|
35
|
+
let K = new Uint8Array(hLen).fill(0);
|
|
36
|
+
const x = bigIntToBytes(privKey, qLen);
|
|
37
|
+
const z = bigIntToBytes(mod(truncateHash(msgHash, curve), curve.n), qLen);
|
|
38
|
+
const concat0 = new Uint8Array(hLen + 1 + qLen + qLen);
|
|
39
|
+
concat0.set(V, 0);
|
|
40
|
+
concat0[hLen] = 0;
|
|
41
|
+
concat0.set(x, hLen + 1);
|
|
42
|
+
concat0.set(z, hLen + 1 + qLen);
|
|
43
|
+
K = hmacDigest(hashAlgo, K, concat0);
|
|
44
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
45
|
+
const concat1 = new Uint8Array(hLen + 1 + qLen + qLen);
|
|
46
|
+
concat1.set(V, 0);
|
|
47
|
+
concat1[hLen] = 1;
|
|
48
|
+
concat1.set(x, hLen + 1);
|
|
49
|
+
concat1.set(z, hLen + 1 + qLen);
|
|
50
|
+
K = hmacDigest(hashAlgo, K, concat1);
|
|
51
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
52
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
53
|
+
let T = new Uint8Array(0);
|
|
54
|
+
while (T.length < qLen) {
|
|
55
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
56
|
+
const newT = new Uint8Array(T.length + V.length);
|
|
57
|
+
newT.set(T, 0);
|
|
58
|
+
newT.set(V, T.length);
|
|
59
|
+
T = newT;
|
|
60
|
+
}
|
|
61
|
+
const k = truncateHash(T.slice(0, qLen), curve);
|
|
62
|
+
if (k >= 1n && k < curve.n) {
|
|
63
|
+
return k;
|
|
64
|
+
}
|
|
65
|
+
const retryConcat = new Uint8Array(hLen + 1);
|
|
66
|
+
retryConcat.set(V, 0);
|
|
67
|
+
retryConcat[hLen] = 0;
|
|
68
|
+
K = hmacDigest(hashAlgo, K, retryConcat);
|
|
69
|
+
V = hmacDigest(hashAlgo, K, V);
|
|
70
|
+
}
|
|
71
|
+
throw new Error("RFC 6979: failed to generate valid k after 100 attempts");
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* ECDSA signature generation per FIPS 186-4 Section 6.4.
|
|
75
|
+
*
|
|
76
|
+
* @param hashAlgo Hash algorithm name (e.g. 'sha256')
|
|
77
|
+
* @param privKeyBytes Private key as big-endian bytes
|
|
78
|
+
* @param data Data to sign (will be hashed)
|
|
79
|
+
* @param curveName Curve name (e.g. 'P-256')
|
|
80
|
+
* @returns Signature as r || s concatenated (each curve.byteLength bytes)
|
|
81
|
+
*/
|
|
73
82
|
function ecdsaSign(hashAlgo, privKeyBytes, data, curveName) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
const curve = getCurve(curveName);
|
|
84
|
+
const G = {
|
|
85
|
+
x: curve.Gx,
|
|
86
|
+
y: curve.Gy
|
|
87
|
+
};
|
|
88
|
+
const d = bytesToBigInt(privKeyBytes);
|
|
89
|
+
const hash = new Hash(hashAlgo);
|
|
90
|
+
hash.update(data);
|
|
91
|
+
const msgHash = new Uint8Array(hash.digest());
|
|
92
|
+
const e = truncateHash(msgHash, curve);
|
|
93
|
+
const k = rfc6979(hashAlgo, d, msgHash, curve);
|
|
94
|
+
const R = scalarMul(k, G, curve);
|
|
95
|
+
if (R === null) throw new Error("ECDSA: k * G is point at infinity");
|
|
96
|
+
const r = mod(R.x, curve.n);
|
|
97
|
+
if (r === 0n) throw new Error("ECDSA: r is zero");
|
|
98
|
+
const kInv = modInverse(k, curve.n);
|
|
99
|
+
const s = mod(kInv * (e + r * d), curve.n);
|
|
100
|
+
if (s === 0n) throw new Error("ECDSA: s is zero");
|
|
101
|
+
const sigLen = curve.byteLength;
|
|
102
|
+
const sig = new Uint8Array(sigLen * 2);
|
|
103
|
+
sig.set(bigIntToBytes(r, sigLen), 0);
|
|
104
|
+
sig.set(bigIntToBytes(s, sigLen), sigLen);
|
|
105
|
+
return sig;
|
|
94
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* ECDSA signature verification per FIPS 186-4 Section 6.4.
|
|
109
|
+
*
|
|
110
|
+
* @param hashAlgo Hash algorithm name (e.g. 'sha256')
|
|
111
|
+
* @param pubKeyBytes Public key as uncompressed point (0x04 || x || y)
|
|
112
|
+
* @param signature Signature as r || s concatenated
|
|
113
|
+
* @param data Signed data (will be hashed)
|
|
114
|
+
* @param curveName Curve name (e.g. 'P-256')
|
|
115
|
+
* @returns true if signature is valid
|
|
116
|
+
*/
|
|
95
117
|
function ecdsaVerify(hashAlgo, pubKeyBytes, signature, data, curveName) {
|
|
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
|
-
|
|
118
|
+
const curve = getCurve(curveName);
|
|
119
|
+
const G = {
|
|
120
|
+
x: curve.Gx,
|
|
121
|
+
y: curve.Gy
|
|
122
|
+
};
|
|
123
|
+
const sigLen = curve.byteLength;
|
|
124
|
+
if (pubKeyBytes[0] !== 4 || pubKeyBytes.length !== 1 + sigLen * 2) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const Qx = bytesToBigInt(pubKeyBytes.slice(1, 1 + sigLen));
|
|
128
|
+
const Qy = bytesToBigInt(pubKeyBytes.slice(1 + sigLen));
|
|
129
|
+
const Q = {
|
|
130
|
+
x: Qx,
|
|
131
|
+
y: Qy
|
|
132
|
+
};
|
|
133
|
+
if (signature.length !== sigLen * 2) return false;
|
|
134
|
+
const r = bytesToBigInt(signature.slice(0, sigLen));
|
|
135
|
+
const s = bytesToBigInt(signature.slice(sigLen));
|
|
136
|
+
if (r < 1n || r >= curve.n || s < 1n || s >= curve.n) return false;
|
|
137
|
+
const hash = new Hash(hashAlgo);
|
|
138
|
+
hash.update(data);
|
|
139
|
+
const msgHash = new Uint8Array(hash.digest());
|
|
140
|
+
const e = truncateHash(msgHash, curve);
|
|
141
|
+
const w = modInverse(s, curve.n);
|
|
142
|
+
const u1 = mod(e * w, curve.n);
|
|
143
|
+
const u2 = mod(r * w, curve.n);
|
|
144
|
+
const R1 = scalarMul(u1, G, curve);
|
|
145
|
+
const R2 = scalarMul(u2, Q, curve);
|
|
146
|
+
const R = pointAdd(R1, R2, curve);
|
|
147
|
+
if (R === null) return false;
|
|
148
|
+
return mod(R.x, curve.n) === r;
|
|
121
149
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
};
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
export { ecdsaSign, ecdsaVerify };
|
package/lib/esm/hash.js
CHANGED
|
@@ -1,100 +1,111 @@
|
|
|
1
|
+
import { normalizeAlgorithm } from "./crypto-utils.js";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
1
3
|
import GLib from "@girs/glib-2.0";
|
|
2
4
|
import { Transform } from "node:stream";
|
|
3
|
-
import { Buffer } from "node:buffer";
|
|
4
5
|
import { normalizeEncoding } from "@gjsify/utils";
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
//#region src/hash.ts
|
|
6
8
|
const CHECKSUM_TYPES = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
md5: GLib.ChecksumType.MD5,
|
|
10
|
+
sha1: GLib.ChecksumType.SHA1,
|
|
11
|
+
sha256: GLib.ChecksumType.SHA256,
|
|
12
|
+
sha384: GLib.ChecksumType.SHA384,
|
|
13
|
+
sha512: GLib.ChecksumType.SHA512
|
|
12
14
|
};
|
|
13
15
|
function getChecksumType(algorithm) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
class Hash extends Transform {
|
|
24
|
-
_algorithm;
|
|
25
|
-
_checksum;
|
|
26
|
-
_finalized = false;
|
|
27
|
-
constructor(algorithm) {
|
|
28
|
-
super();
|
|
29
|
-
const normalized = normalizeAlgorithm(algorithm);
|
|
30
|
-
const type = getChecksumType(normalized);
|
|
31
|
-
this._algorithm = normalized;
|
|
32
|
-
this._checksum = new GLib.Checksum(type);
|
|
33
|
-
}
|
|
34
|
-
/** Update the hash with data. */
|
|
35
|
-
update(data, inputEncoding) {
|
|
36
|
-
if (this._finalized) {
|
|
37
|
-
throw new Error("Digest already called");
|
|
38
|
-
}
|
|
39
|
-
let bytes;
|
|
40
|
-
if (typeof data === "string") {
|
|
41
|
-
const enc = normalizeEncoding(inputEncoding);
|
|
42
|
-
bytes = Buffer.from(data, enc);
|
|
43
|
-
} else {
|
|
44
|
-
bytes = data instanceof Uint8Array ? data : Buffer.from(data);
|
|
45
|
-
}
|
|
46
|
-
this._checksum.update(bytes);
|
|
47
|
-
return this;
|
|
48
|
-
}
|
|
49
|
-
/** Calculate the digest of all data passed to update(). */
|
|
50
|
-
digest(encoding) {
|
|
51
|
-
if (this._finalized) {
|
|
52
|
-
throw new Error("Digest already called");
|
|
53
|
-
}
|
|
54
|
-
this._finalized = true;
|
|
55
|
-
const hexStr = this._checksum.get_string();
|
|
56
|
-
const buf = Buffer.from(hexStr, "hex");
|
|
57
|
-
if (encoding) return buf.toString(encoding);
|
|
58
|
-
return buf;
|
|
59
|
-
}
|
|
60
|
-
/** Copy this hash to a new Hash object. */
|
|
61
|
-
copy() {
|
|
62
|
-
if (this._finalized) {
|
|
63
|
-
throw new Error("Digest already called");
|
|
64
|
-
}
|
|
65
|
-
const copy = Object.create(Hash.prototype);
|
|
66
|
-
Transform.call(copy);
|
|
67
|
-
copy._algorithm = this._algorithm;
|
|
68
|
-
copy._finalized = false;
|
|
69
|
-
copy._checksum = this._checksum.copy();
|
|
70
|
-
return copy;
|
|
71
|
-
}
|
|
72
|
-
// Transform stream interface
|
|
73
|
-
_transform(chunk, encoding, callback) {
|
|
74
|
-
try {
|
|
75
|
-
this.update(chunk, encoding);
|
|
76
|
-
callback();
|
|
77
|
-
} catch (err) {
|
|
78
|
-
callback(err);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
_flush(callback) {
|
|
82
|
-
try {
|
|
83
|
-
this.push(this.digest());
|
|
84
|
-
callback();
|
|
85
|
-
} catch (err) {
|
|
86
|
-
callback(err);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
16
|
+
const normalized = normalizeAlgorithm(algorithm);
|
|
17
|
+
const type = CHECKSUM_TYPES[normalized];
|
|
18
|
+
if (type === undefined) {
|
|
19
|
+
const err = new Error(`Unknown message digest: ${algorithm}`);
|
|
20
|
+
err.code = "ERR_CRYPTO_HASH_UNKNOWN";
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
return type;
|
|
89
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates and returns a Hash object that can be used to generate hash digests
|
|
27
|
+
* using the given algorithm.
|
|
28
|
+
*/
|
|
29
|
+
var Hash = class Hash extends Transform {
|
|
30
|
+
_algorithm;
|
|
31
|
+
_checksum;
|
|
32
|
+
_finalized = false;
|
|
33
|
+
constructor(algorithm) {
|
|
34
|
+
super();
|
|
35
|
+
const normalized = normalizeAlgorithm(algorithm);
|
|
36
|
+
const type = getChecksumType(normalized);
|
|
37
|
+
this._algorithm = normalized;
|
|
38
|
+
this._checksum = new GLib.Checksum(type);
|
|
39
|
+
}
|
|
40
|
+
/** Update the hash with data. */
|
|
41
|
+
update(data, inputEncoding) {
|
|
42
|
+
if (this._finalized) {
|
|
43
|
+
throw new Error("Digest already called");
|
|
44
|
+
}
|
|
45
|
+
let bytes;
|
|
46
|
+
if (typeof data === "string") {
|
|
47
|
+
const enc = normalizeEncoding(inputEncoding);
|
|
48
|
+
bytes = Buffer.from(data, enc);
|
|
49
|
+
} else {
|
|
50
|
+
bytes = data instanceof Uint8Array ? data : Buffer.from(data);
|
|
51
|
+
}
|
|
52
|
+
this._checksum.update(bytes);
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
/** Calculate the digest of all data passed to update(). */
|
|
56
|
+
digest(encoding) {
|
|
57
|
+
if (this._finalized) {
|
|
58
|
+
throw new Error("Digest already called");
|
|
59
|
+
}
|
|
60
|
+
this._finalized = true;
|
|
61
|
+
const hexStr = this._checksum.get_string();
|
|
62
|
+
const buf = Buffer.from(hexStr, "hex");
|
|
63
|
+
if (encoding) return buf.toString(encoding);
|
|
64
|
+
return buf;
|
|
65
|
+
}
|
|
66
|
+
/** Copy this hash to a new Hash object. */
|
|
67
|
+
copy() {
|
|
68
|
+
if (this._finalized) {
|
|
69
|
+
throw new Error("Digest already called");
|
|
70
|
+
}
|
|
71
|
+
const copy = Object.create(Hash.prototype);
|
|
72
|
+
Transform.call(copy);
|
|
73
|
+
copy._algorithm = this._algorithm;
|
|
74
|
+
copy._finalized = false;
|
|
75
|
+
copy._checksum = this._checksum.copy();
|
|
76
|
+
return copy;
|
|
77
|
+
}
|
|
78
|
+
_transform(chunk, encoding, callback) {
|
|
79
|
+
try {
|
|
80
|
+
this.update(chunk, encoding);
|
|
81
|
+
callback();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
callback(err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
_flush(callback) {
|
|
87
|
+
try {
|
|
88
|
+
this.push(this.digest());
|
|
89
|
+
callback();
|
|
90
|
+
} catch (err) {
|
|
91
|
+
callback(err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/** Get the list of supported hash algorithms. */
|
|
90
96
|
function getHashes() {
|
|
91
|
-
|
|
97
|
+
return [
|
|
98
|
+
"md5",
|
|
99
|
+
"sha1",
|
|
100
|
+
"sha256",
|
|
101
|
+
"sha384",
|
|
102
|
+
"sha512"
|
|
103
|
+
];
|
|
92
104
|
}
|
|
105
|
+
/** Convenience: one-shot hash (Node 21+). */
|
|
93
106
|
function hash(algorithm, data, encoding) {
|
|
94
|
-
|
|
107
|
+
return new Hash(algorithm).update(data).digest(encoding);
|
|
95
108
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
hash
|
|
100
|
-
};
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
export { Hash, getHashes, hash };
|
package/lib/esm/hkdf.js
CHANGED
|
@@ -1,58 +1,76 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DIGEST_SIZES, SUPPORTED_ALGORITHMS, normalizeAlgorithm, toBuffer } from "./crypto-utils.js";
|
|
2
2
|
import { Hmac } from "./hmac.js";
|
|
3
|
-
import {
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
|
|
5
|
+
//#region src/hkdf.ts
|
|
4
6
|
function hmacDigest(algo, key, data) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const hmac = new Hmac(algo, key);
|
|
8
|
+
hmac.update(data);
|
|
9
|
+
return hmac.digest();
|
|
8
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* HKDF-Extract: PRK = HMAC-Hash(salt, IKM)
|
|
13
|
+
*/
|
|
9
14
|
function hkdfExtract(algo, ikm, salt) {
|
|
10
|
-
|
|
15
|
+
return hmacDigest(algo, salt, ikm);
|
|
11
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* HKDF-Expand: OKM = T(1) || T(2) || ... || T(N)
|
|
19
|
+
* T(i) = HMAC-Hash(PRK, T(i-1) || info || i)
|
|
20
|
+
*/
|
|
12
21
|
function hkdfExpand(algo, prk, info, length, hashLen) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const n = Math.ceil(length / hashLen);
|
|
23
|
+
if (n > 255) {
|
|
24
|
+
throw new Error("HKDF cannot generate more than 255 * HashLen bytes");
|
|
25
|
+
}
|
|
26
|
+
const okm = Buffer.allocUnsafe(n * hashLen);
|
|
27
|
+
let prev = Buffer.alloc(0);
|
|
28
|
+
for (let i = 1; i <= n; i++) {
|
|
29
|
+
const input = Buffer.concat([
|
|
30
|
+
prev,
|
|
31
|
+
info,
|
|
32
|
+
Buffer.from([i])
|
|
33
|
+
]);
|
|
34
|
+
prev = hmacDigest(algo, prk, input);
|
|
35
|
+
prev.copy(okm, (i - 1) * hashLen);
|
|
36
|
+
}
|
|
37
|
+
return Buffer.from(okm.buffer, okm.byteOffset, length);
|
|
25
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Synchronous HKDF key derivation.
|
|
41
|
+
*/
|
|
26
42
|
function hkdfSync(digest, ikm, salt, info, keylen) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const algo = normalizeAlgorithm(digest);
|
|
44
|
+
const hashLen = DIGEST_SIZES[algo];
|
|
45
|
+
if (!SUPPORTED_ALGORITHMS.has(algo) || hashLen === undefined) {
|
|
46
|
+
throw new TypeError(`Unknown message digest: ${digest}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof keylen !== "number" || keylen < 0) {
|
|
49
|
+
throw new TypeError("keylen must be a non-negative number");
|
|
50
|
+
}
|
|
51
|
+
const ikmBuf = toBuffer(ikm);
|
|
52
|
+
const saltBuf = toBuffer(salt);
|
|
53
|
+
const infoBuf = toBuffer(info);
|
|
54
|
+
const effectiveSalt = saltBuf.length === 0 ? Buffer.alloc(hashLen, 0) : saltBuf;
|
|
55
|
+
const prk = hkdfExtract(algo, ikmBuf, effectiveSalt);
|
|
56
|
+
const okm = hkdfExpand(algo, prk, infoBuf, keylen, hashLen);
|
|
57
|
+
const result = new ArrayBuffer(okm.length);
|
|
58
|
+
new Uint8Array(result).set(okm);
|
|
59
|
+
return result;
|
|
44
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Asynchronous HKDF key derivation.
|
|
63
|
+
*/
|
|
45
64
|
function hkdf(digest, ikm, salt, info, keylen, callback) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
try {
|
|
67
|
+
const result = hkdfSync(digest, ikm, salt, info, keylen);
|
|
68
|
+
callback(null, result);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
callback(err instanceof Error ? err : new Error(String(err)));
|
|
71
|
+
}
|
|
72
|
+
}, 0);
|
|
54
73
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { hkdf, hkdfSync };
|