Password hashing & JSON Web Token in NodeJS.
0
crypto.node.md
edited
1# Password hashing
2
3<details>
4<summary><h4>Scrypt & Argon2</h4></summary>
5
6```js
7import crypto from 'node:crypto';
8
9const PASSWORD = '12345678';
10const SALT = crypto.randomBytes(16); // unique for each password
11
12const NORMALIZED_PASSWORD = PASSWORD.slice(-128).normalize('NFKC');
13
14console.log('Salt:', SALT.toString('hex'), '\n');
15
16//////////////////////////////////////////////////
17// SCRYPT (Node.js v10.9)
18//////////////////////////////////////////////////
19
20const hashScryptBuffer = crypto.scryptSync(
21 NORMALIZED_PASSWORD,
22 SALT,
23 64, // keylen
24 {
25 N: 16384, // CPU/memory cost
26 r: 8, // Block size
27 p: 1 // Parallelization
28 }
29);
30
31const hashScryptHex = hashScryptBuffer.toString('hex');
32
33console.log('Scrypt:', hashScryptHex);
34
35const hashScryptIsEqual = crypto.timingSafeEqual(
36 Buffer.from(hashScryptHex, 'hex'),
37 hashScryptBuffer
38);
39
40console.log('Verify:', hashScryptIsEqual, '\n');
41
42//////////////////////////////////////////////////
43// ARGON2 (Node.js v24.7)
44//////////////////////////////////////////////////
45
46const hashArgon2Buffer = crypto.argon2Sync(
47 'argon2id',
48 {
49 message: NORMALIZED_PASSWORD,
50 nonce: SALT,
51 parallelism: 4,
52 tagLength: 64,
53 memory: 65536,
54 passes: 3
55 }
56);
57
58const hashArgon2Hex = hashArgon2Buffer.toString('hex');
59
60console.log('Argon2:', hashArgon2Hex);
61
62const hashArgon2IsEqual = crypto.timingSafeEqual(
63 Buffer.from(hashArgon2Hex, 'hex'),
64 hashArgon2Buffer
65);
66
67console.log('Verify:', hashArgon2IsEqual);
68```
69
70</details>
71
72# JSON Web Token (JWT)
73
74Internet standard for creating data with optional signature whose payload holds JSON that asserts some number of claims.
75
76<https://jwt.io>
77
78<details>
79<summary><h4>RSA PKCS1-v1.5 (1993)</h4></summary>
80
81```js
82import crypto from 'node:crypto';
83
84// Base 64 Encoding with URL and Filename Safe Alphabet.
85function toBase64Url(data) {
86 if (Buffer.isBuffer(data))
87 return data.toString('base64url');
88 if (typeof(data) !== 'string')
89 data = JSON.stringify(data);
90 return toBase64Url(Buffer.from(data));
91}
92
93function fromBase64Url(data, parseJson) {
94 data = Buffer.from(data, 'base64url');
95 if (parseJson != null)
96 data = data.toString('utf8');
97 return parseJson ? JSON.parse(data) : data;
98}
99
100//////////////////////////////////////////////////
101// JWT: HEADER & PAYLOAD
102//////////////////////////////////////////////////
103
104// RSA PKCS1-v1.5 (1993).
105//
106// RSA: Rivest–Shamir–Adleman
107// PKCS1: Public Key Cryptography Standard #1
108
109const ALG = 'RS256'; // 'RS256' | 'RS384' | 'RS512'
110const ALGORITHM = 'SHA256'; // 'SHA256' | 'SHA384' | 'SHA512'
111
112const header = { alg: ALG, typ: 'JWT' };
113const payload = { data: 'Hello World!' };
114
115//////////////////////////////////////////////////
116// KEYS
117//////////////////////////////////////////////////
118
119const { publicKey, privateKey } = crypto.generateKeyPairSync(
120 'rsa',
121 {
122 modulusLength: 3072, // ≥2048
123 publicKeyEncoding: {
124 type: 'spki',
125 format: 'pem'
126 },
127 privateKeyEncoding: {
128 type: 'pkcs8',
129 format: 'pem'
130 },
131 }
132);
133
134const PADDING = crypto.constants.RSA_PKCS1_PADDING;
135
136const PUBLIC_KEY = { key: publicKey, padding: PADDING };
137const PRIVATE_KEY = { key: privateKey, padding: PADDING };
138
139console.log(`publicKey:\n${publicKey}`);
140
141//////////////////////////////////////////////////
142// SIGN
143//////////////////////////////////////////////////
144
145const headerBase64Url = toBase64Url(header);
146const payloadBase64Url = toBase64Url(payload);
147
148const data = `${headerBase64Url}.${payloadBase64Url}`;
149const signature = crypto.sign(ALGORITHM, data, PRIVATE_KEY);
150const signatureBase64Url = toBase64Url(signature);
151
152const jsonWebToken = `${data}.${signatureBase64Url}`;
153console.log('Token:', jsonWebToken, '\n');
154
155//////////////////////////////////////////////////
156// VERIFY
157//////////////////////////////////////////////////
158
159const jwtParts = jsonWebToken.split('.');
160
161const jwtHeaderBase64Url = jwtParts[0];
162const jwtPayloadBase64Url = jwtParts[1];
163const jwtSignatureBase64Url = jwtParts[2];
164
165const jwtHeaderObject = fromBase64Url(jwtHeaderBase64Url, true);
166const jwtPayloadObject = fromBase64Url(jwtPayloadBase64Url, true);
167const jwtSignatureBuffer = fromBase64Url(jwtSignatureBase64Url);
168
169if (jwtHeaderObject.alg !== header.alg)
170 throw new Error('Invalid algorithm');
171
172const jwtData = `${jwtHeaderBase64Url}.${jwtPayloadBase64Url}`;
173const isValid = crypto.verify(ALGORITHM, jwtData, PUBLIC_KEY, jwtSignatureBuffer);
174
175console.log('Verify:', isValid); // true
176console.log('Header:', jwtHeaderObject); // { alg: 'RS256', typ: 'JWT' }
177console.log('Payload:', jwtPayloadObject); // { data: 'Hello World!' }
178```
179
180</details>
181
182<details>
183<summary><h4>RSA-PSS PKCS1-v2.1 (2002)</h4></summary>
184
185```js
186import crypto from 'node:crypto';
187
188// Base 64 Encoding with URL and Filename Safe Alphabet.
189function toBase64Url(data) {
190 if (Buffer.isBuffer(data))
191 return data.toString('base64url');
192 if (typeof(data) !== 'string')
193 data = JSON.stringify(data);
194 return toBase64Url(Buffer.from(data));
195}
196
197function fromBase64Url(data, parseJson) {
198 data = Buffer.from(data, 'base64url');
199 if (parseJson != null)
200 data = data.toString('utf8');
201 return parseJson ? JSON.parse(data) : data;
202}
203
204//////////////////////////////////////////////////
205// JWT: HEADER & PAYLOAD
206//////////////////////////////////////////////////
207
208// RSA-PSS PKCS1-v2.1 (2002)
209//
210// RSA: Rivest–Shamir–Adleman
211// PSS: Probabilistic Signature Scheme
212// PKCS1: Public Key Cryptography Standard #1
213
214const ALG = 'PS256'; // 'PS256' | 'PS384' | 'PS512'
215const ALGORITHM = 'SHA256'; // 'SHA256' | 'SHA384' | 'SHA512'
216
217const header = { alg: ALG, typ: 'JWT' };
218const payload = { data: 'Hello World!' };
219
220//////////////////////////////////////////////////
221// KEYS
222//////////////////////////////////////////////////
223
224const { publicKey, privateKey } = crypto.generateKeyPairSync(
225 'rsa',
226 {
227 modulusLength: 3072, // ≥2048
228 publicKeyEncoding: {
229 type: 'spki',
230 format: 'pem'
231 },
232 privateKeyEncoding: {
233 type: 'pkcs8',
234 format: 'pem'
235 },
236 }
237);
238
239const PADDING = crypto.constants.RSA_PKCS1_PSS_PADDING; // MGF1
240const SALTLENGTH = crypto.constants.RSA_PSS_SALTLEN_DIGEST;
241
242const PUBLIC_KEY = { key: publicKey, padding: PADDING, saltLength: SALTLENGTH };
243const PRIVATE_KEY = { key: privateKey, padding: PADDING, saltLength: SALTLENGTH };
244
245console.log(`publicKey:\n${publicKey}`);
246
247//////////////////////////////////////////////////
248// SIGN
249//////////////////////////////////////////////////
250
251const headerBase64Url = toBase64Url(header);
252const payloadBase64Url = toBase64Url(payload);
253
254const data = `${headerBase64Url}.${payloadBase64Url}`;
255const signature = crypto.sign(ALGORITHM, data, PRIVATE_KEY);
256const signatureBase64Url = toBase64Url(signature);
257
258const jsonWebToken = `${data}.${signatureBase64Url}`;
259console.log('Token:', jsonWebToken, '\n');
260
261//////////////////////////////////////////////////
262// VERIFY
263//////////////////////////////////////////////////
264
265const jwtParts = jsonWebToken.split('.');
266
267const jwtHeaderBase64Url = jwtParts[0];
268const jwtPayloadBase64Url = jwtParts[1];
269const jwtSignatureBase64Url = jwtParts[2];
270
271const jwtHeaderObject = fromBase64Url(jwtHeaderBase64Url, true);
272const jwtPayloadObject = fromBase64Url(jwtPayloadBase64Url, true);
273const jwtSignatureBuffer = fromBase64Url(jwtSignatureBase64Url);
274
275if (jwtHeaderObject.alg !== header.alg)
276 throw new Error('Invalid algorithm');
277
278const jwtData = `${jwtHeaderBase64Url}.${jwtPayloadBase64Url}`;
279const isValid = crypto.verify(ALGORITHM, jwtData, PUBLIC_KEY, jwtSignatureBuffer);
280
281console.log('Verify:', isValid); // true
282console.log('Header:', jwtHeaderObject); // { alg: 'PS256', typ: 'JWT' }
283console.log('Payload:', jwtPayloadObject); // { data: 'Hello World!' }
284```
285
286</details>
287
288<details>
289<summary><h4>Elliptic Curve Digital Signature Algorithm (ECDSA)</h4></summary>
290
291```js
292import crypto from 'node:crypto';
293
294// Base 64 Encoding with URL and Filename Safe Alphabet.
295function toBase64Url(data) {
296 if (Buffer.isBuffer(data))
297 return data.toString('base64url');
298 if (typeof(data) !== 'string')
299 data = JSON.stringify(data);
300 return toBase64Url(Buffer.from(data));
301}
302
303function fromBase64Url(data, parseJson) {
304 data = Buffer.from(data, 'base64url');
305 if (parseJson != null)
306 data = data.toString('utf8');
307 return parseJson ? JSON.parse(data) : data;
308}
309
310//////////////////////////////////////////////////
311// JWT: HEADER & PAYLOAD
312//////////////////////////////////////////////////
313
314// Elliptic Curve Digital Signature Algorithm (ECDSA)
315
316const ALG = 'ES256'; // 'ES256' | 'ES384' | 'ES512'
317const NAMED_CURVE = 'P-256'; // 'P-256' | 'P-384' | 'P-512'
318const ALGORITHM = 'SHA256'; // 'SHA256' | 'SHA384' | 'SHA512'
319
320const header = { alg: ALG, typ: 'JWT' };
321const payload = { data: 'Hello World!' };
322
323//////////////////////////////////////////////////
324// KEYS
325//////////////////////////////////////////////////
326
327const { publicKey, privateKey } = crypto.generateKeyPairSync(
328 'ec',
329 {
330 namedCurve: NAMED_CURVE,
331 publicKeyEncoding: {
332 type: 'spki',
333 format: 'pem'
334 },
335 privateKeyEncoding: {
336 type: 'pkcs8',
337 format: 'pem'
338 },
339 }
340);
341
342console.log(`publicKey:\n${publicKey}`);
343
344//////////////////////////////////////////////////
345// SIGN
346//////////////////////////////////////////////////
347
348const headerBase64Url = toBase64Url(header);
349const payloadBase64Url = toBase64Url(payload);
350
351const data = `${headerBase64Url}.${payloadBase64Url}`;
352const signature = crypto.sign(ALGORITHM, data, privateKey);
353const signatureBase64Url = toBase64Url(signature);
354
355const jsonWebToken = `${data}.${signatureBase64Url}`;
356console.log('Token:', jsonWebToken, '\n');
357
358//////////////////////////////////////////////////
359// VERIFY
360//////////////////////////////////////////////////
361
362const jwtParts = jsonWebToken.split('.');
363
364const jwtHeaderBase64Url = jwtParts[0];
365const jwtPayloadBase64Url = jwtParts[1];
366const jwtSignatureBase64Url = jwtParts[2];
367
368const jwtHeaderObject = fromBase64Url(jwtHeaderBase64Url, true);
369const jwtPayloadObject = fromBase64Url(jwtPayloadBase64Url, true);
370const jwtSignatureBuffer = fromBase64Url(jwtSignatureBase64Url);
371
372if (jwtHeaderObject.alg !== header.alg)
373 throw new Error('Invalid algorithm');
374
375const jwtData = `${jwtHeaderBase64Url}.${jwtPayloadBase64Url}`;
376const isValid = crypto.verify(ALGORITHM, jwtData, publicKey, jwtSignatureBuffer);
377
378console.log('Verify:', isValid); // true
379console.log('Header:', jwtHeaderObject); // { alg: 'ES256', typ: 'JWT' }
380console.log('Payload:', jwtPayloadObject); // { data: 'Hello World!' }
381```
382
383</details>
384
385<details>
386<summary><h4>Module-Lattice-Based Digital Signature Algorithm (MLDSA)</h4></summary>
387
388Node.js v24.7.0.
389
390```js
391import crypto from 'node:crypto';
392
393// Base 64 Encoding with URL and Filename Safe Alphabet.
394function toBase64Url(data) {
395 if (Buffer.isBuffer(data))
396 return data.toString('base64url');
397 if (typeof(data) !== 'string')
398 data = JSON.stringify(data);
399 return toBase64Url(Buffer.from(data));
400}
401
402function fromBase64Url(data, parseJson) {
403 data = Buffer.from(data, 'base64url');
404 if (parseJson != null)
405 data = data.toString('utf8');
406 return parseJson ? JSON.parse(data) : data;
407}
408
409//////////////////////////////////////////////////
410// JWT: HEADER & PAYLOAD
411//////////////////////////////////////////////////
412
413// Post-Quantum Cryptography (NIST FIPS 204, ML-DSA)
414// Module-Lattice-Based Digital Signature Algorithm (MLDSA)
415
416const ALG = 'MLDSA44'; // 'MLDSA44' | 'MLDSA65' | 'MLDSA87'
417const KEY_TYPE = 'ml-dsa-44'; // 'ml-dsa-44' | 'ml-dsa-65' | 'ml-dsa-87'
418
419const header = { alg: ALG, typ: 'JWT' };
420const payload = { data: 'Hello World!' };
421
422//////////////////////////////////////////////////
423// KEYS
424//////////////////////////////////////////////////
425
426const { publicKey, privateKey } = crypto.generateKeyPairSync(
427 KEY_TYPE,
428 {
429 publicKeyEncoding: {
430 type: 'spki',
431 format: 'pem'
432 },
433 privateKeyEncoding: {
434 type: 'pkcs8',
435 format: 'pem'
436 },
437 }
438);
439
440console.log(`publicKey:\n${publicKey}`);
441
442//////////////////////////////////////////////////
443// SIGN
444//////////////////////////////////////////////////
445
446const headerBase64Url = toBase64Url(header);
447const payloadBase64Url = toBase64Url(payload);
448
449const data = `${headerBase64Url}.${payloadBase64Url}`;
450const signature = crypto.sign(null, data, privateKey);
451const signatureBase64Url = toBase64Url(signature);
452
453const jsonWebToken = `${data}.${signatureBase64Url}`;
454console.log(`Token:\n${jsonWebToken}\n`);
455
456//////////////////////////////////////////////////
457// VERIFY
458//////////////////////////////////////////////////
459
460const jwtParts = jsonWebToken.split('.');
461
462const jwtHeaderBase64Url = jwtParts[0];
463const jwtPayloadBase64Url = jwtParts[1];
464const jwtSignatureBase64Url = jwtParts[2];
465
466const jwtHeaderObject = fromBase64Url(jwtHeaderBase64Url, true);
467const jwtPayloadObject = fromBase64Url(jwtPayloadBase64Url, true);
468const jwtSignatureBuffer = fromBase64Url(jwtSignatureBase64Url);
469
470if (jwtHeaderObject.alg !== header.alg)
471 throw new Error('Invalid algorithm');
472
473const jwtData = `${jwtHeaderBase64Url}.${jwtPayloadBase64Url}`;
474const isValid = crypto.verify(null, jwtData, publicKey, jwtSignatureBuffer);
475
476console.log('Verify:', isValid); // true
477console.log('Header:', jwtHeaderObject); // { alg: 'MLDSA44', typ: 'JWT' }
478console.log('Payload:', jwtPayloadObject); // { data: 'Hello World!' }
479```
480
481</details>