Password hashing & JSON Web Token in NodeJS.
0
crypto.node.md edited
481 lines 14 kB View raw View rendered
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>
Sign up or login to add to the discussion