Last active
May 30, 2024 14:31
-
-
Save nakov/5bfce93736468884a53e0ab2be06641e to your computer and use it in GitHub Desktop.
Cryptography for JavaScript Developers: Hashes, HMAC, PBKDF2, Scrypt, Argon2, AES-256-CTR, ECDSA, EdDSA, secp256k1, Ed25519
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const aes = require("aes-js"); | |
const argon2 = require("argon2"); | |
const crypto = require("crypto"); | |
const cryptoJS = require("crypto-js"); | |
// Encrypt using AES-256-CTR-Argon2-HMAC-SHA-256 | |
async function aes256ctrEncrypt(plaintext, password) { | |
let argon2salt = crypto.randomBytes(16); // 128-bit salt for argon2 | |
let argon2Settings = { type: argon2.argon2di, raw: true, | |
timeCost: 8, memoryCost: 2 ** 15, parallelism: 2, | |
hashLength: 32, salt: argon2salt }; | |
let secretKey = await argon2.hash(password, argon2Settings); | |
console.log("Derived Argon2 encryption key:", secretKey.toString('hex')); | |
let plainTextBytes = aes.utils.utf8.toBytes(plaintext); | |
let aesIV = crypto.randomBytes(16); // 128-bit initial vector (salt) | |
let aesCTR = new aes.ModeOfOperation.ctr(secretKey, new aes.Counter(aesIV)); | |
let ciphertextBytes = aesCTR.encrypt(plainTextBytes); | |
let ciphertextHex = aes.utils.hex.fromBytes(ciphertextBytes); | |
let hmac = cryptoJS.HmacSHA256(plaintext, secretKey.toString('hex')); | |
return { | |
kdf: 'argon2', kdfSettings: { salt: argon2salt.toString('hex') }, | |
cipher: 'aes-256-ctr', cipherSettings: {iv: aesIV.toString('hex') }, | |
ciphertext: ciphertextHex, mac: hmac.toString() | |
} | |
} | |
// Decrypt using AES-256-CTR-Argon2-HMAC-SHA-256 | |
async function aes256ctrDecrypt(encryptedMsg, password) { | |
let saltBytes = Buffer.from(encryptedMsg.kdfSettings.salt, 'hex'); | |
let argon2Settings = { type: argon2.argon2di, raw: true, | |
timeCost: 8, memoryCost: 2 ** 15, parallelism: 2, | |
hashLength: 32, salt: saltBytes }; | |
let secretKey = await argon2.hash(password, argon2Settings); | |
console.log("Derived Argon2 decryption key:", secretKey.toString('hex')); | |
let aesCTR = new aes.ModeOfOperation.ctr(secretKey, | |
new aes.Counter(Buffer.from(encryptedMsg.cipherSettings.iv, 'hex'))); | |
let decryptedBytes = aesCTR.decrypt( | |
Buffer.from(encryptedMsg.ciphertext, 'hex')); | |
let decryptedPlaintext = aes.utils.utf8.fromBytes(decryptedBytes); | |
let hmac = cryptoJS.HmacSHA256(decryptedPlaintext, secretKey.toString('hex')); | |
if (hmac != encryptedMsg.mac) | |
throw new Error('MAC does not match: maybe wrong password'); | |
return decryptedPlaintext; | |
} | |
(async () => { | |
let encryptedMsg = await aes256ctrEncrypt("some text", "pass@123"); | |
console.log("Encrypted msg:", encryptedMsg); | |
let decryptedPlainText = await aes256ctrDecrypt(encryptedMsg, "pass@123"); | |
console.log("Successfully decrypted:", decryptedPlainText); | |
try { | |
await aes256ctrDecrypt(encryptedMsg, "wrong!Pass"); | |
} catch (error) { | |
console.log(error.message); | |
} | |
})(); | |
// Output: | |
// Derived Argon2 encryption key: 2c695b63f7ae8cfc1701910694fb0d087bd30810dcfe1692d15f6d61d27716a8 | |
// Encrypted msg: { kdf: 'argon2', | |
// kdfSettings: { salt: 'e484d60ea0a365c257e0a78a928d130f' }, | |
// cipher: 'aes-256-ctr', | |
// cipherSettings: { iv: '1c2b12fc5e50014cba8751d48299e92f' }, | |
// ciphertext: 'f4128e2de7ab6d5d26', | |
// mac: 'ea9393dec2aab69a6724904eb725846f532d4fb469fdb0ecc75b605128fbe34d' } | |
// Derived Argon2 decryption key: 2c695b63f7ae8cfc1701910694fb0d087bd30810dcfe1692d15f6d61d27716a8 | |
// Successfully decrypted: some text | |
// Derived Argon2 decryption key: fe5daf262e59124021c6ecb3ed915d9c143aca442f5bfec9ea6108a15a5d06be | |
// MAC does not match: maybe wrong password |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
argon2 = require("argon2"); | |
(async () => { | |
let settings = {raw: true, type: argon2.argon2di, timeCost: 16, | |
memoryCost: 2 ** 15, parallelism: 2, hashLength: 32, | |
salt: Buffer("some salt")}; | |
let argon2Key = await argon2.hash("password", settings); | |
console.log("Argon2 derive key:", argon2Key.toString('hex')); | |
settings = {type: argon2.argon2di, timeCost: 16, | |
memoryCost: 2 ** 15, parallelism: 2}; // salt will be random | |
let argon2Hash = await argon2.hash("password", settings); | |
console.log("Argon2 hash (random salt):", argon2Hash); | |
console.log("Password 'password' correct?", | |
await argon2.verify(argon2Hash, "password")); | |
console.log("Password 'wrong123' correct?", | |
await argon2.verify(argon2Hash, "wrong123")); | |
})(); | |
// Output: | |
// Argon2 derive key: 1ed3694706b2a49b8031836fd501152c386495f9a669481b74ad30732f55423b | |
// Argon2 hash (random salt): $argon2d$v=19$m=32768,t=16,p=2$Efm/rWF3bxnSlQg6sRw28Q$JLgwVXNMcP3u3P4bBDB1ujERgvxkibmsHPiqO/2FV/I | |
// Password 'password' correct? true | |
// Password 'wrong123' correct? false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const EC = require('elliptic').ec; | |
const ec = new EC('secp256k1'); // 256-bit curve: `secp256k1` | |
const cryptoJS = require("crypto-js"); | |
// Generate keys | |
let keyPair = ec.genKeyPair(); | |
console.log("Private key (256 bits):", keyPair.getPrivate("hex")); | |
console.log("Public key (512 bits): ", keyPair.getPublic("hex")); | |
console.log("Public key (compressed, 257 bits):", | |
keyPair.getPublic().encodeCompressed("hex")); | |
// Sign message | |
let msg = "Msg to be signed"; | |
let msgHash = cryptoJS.SHA256(msg).toString(); | |
let signature = | |
ec.sign(msgHash, keyPair.getPrivate(), "hex", {canonical: true}); | |
console.log(`(r=${signature.r}, s=${signature.s}, v=${signature.recoveryParam})`); | |
// Verify signature | |
let validSig = ec.verify( | |
msgHash, signature, keyPair.getPublic()); | |
console.log("Signature valid (correct key)?", validSig); | |
let validSigWrongKey = ec.verify( | |
msgHash, signature, ec.genKeyPair().getPublic()); | |
console.log("Signature valid (wrong key)?", validSigWrongKey); | |
let hexToDecimal = (x) => ec.keyFromPrivate(x, "hex") | |
.getPrivate().toString(10); | |
let pubKeyRecovered = ec.recoverPubKey( | |
hexToDecimal(msgHash), signature, | |
signature.recoveryParam, "hex"); | |
console.log("Recovered pubKey:", | |
pubKeyRecovered.encodeCompressed("hex")); | |
let validSigPK = ec.verify( | |
msgHash, signature, pubKeyRecovered); | |
console.log("Signature valid (recovered key)?", validSigPK); | |
// Output: | |
// Private key (256 bits): 166cbcb63f613645df7c773a8a7b76103f329943a1a49c1fe10de694db109557 | |
// Public key (512 bits): 04f59002e6697ec5ef794e6d03c73b23503e2bb43cde9e089468773a4eb9d85efb92a6847fa114077733389fa5a39402ca2a4573ce2a91998b0f6273c767c2ab84 | |
// Public key (compressed, 257 bits): 02f59002e6697ec5ef794e6d03c73b23503e2bb43cde9e089468773a4eb9d85efb | |
// (r=1897062889226951357652590472390874116440953950286792279211170127401533397136, s=22676265354218592780856375379860805109776725255256408551810079880452835078820, v=0) | |
// Signature valid (correct key)? true | |
// Signature valid (wrong key)? false | |
// Recovered pubKey: 02f59002e6697ec5ef794e6d03c73b23503e2bb43cde9e089468773a4eb9d85efb | |
// Signature valid (recovered key)? true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const EC = require('elliptic').ec; | |
const ec = new EC('ed25519'); // 256-bit curve: `secp256k1` | |
const cryptoJS = require("crypto-js"); | |
// Generate keys | |
let keyPair = ec.genKeyPair(); | |
console.log("Private key (256 bits):", keyPair.getPrivate("hex")); | |
console.log("Public key (compressed, 256 bits):", | |
keyPair.getPublic().encodeCompressed("hex")); | |
// Sign message | |
let msg = "Msg to be signed"; | |
let msgHash = cryptoJS.SHA256(msg).toString(); | |
let signature = | |
ec.sign(msgHash, keyPair.getPrivate(), "hex"); | |
console.log(`(r=${signature.r}, s=${signature.s})`); | |
// Verify signature | |
let validSig = ec.verify( | |
msgHash, signature, keyPair.getPublic()); | |
console.log("Signature valid?", validSig); | |
// Output: | |
// Private key (256 bits): 08a176cb7e38f36679c798d1a24feff766a78a9b5019006d922758b13e684d5e | |
// Public key (compressed, 256 bits): 0305c38d4f5cb96f83eb45224ceac74aed2a19eaf005d54519de77b8681c35a2a6 | |
// (r=488190748947805157450628917774386284742507234334278971094831553541518954229, s=7202979389581348753339390193733324799507876820345609590903975111351095577667) | |
// Signature valid? true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const cryptoJS = require("crypto-js"); | |
console.log("SHA-256:", cryptoJS.SHA256("hello").toString()); | |
console.log("Keccak-256:", cryptoJS.SHA3("hello", {outputLength: 256}).toString()); | |
console.log("RIPEMD-160:", cryptoJS.RIPEMD160("hello").toString()); | |
console.log("Keccak-512:", cryptoJS.SHA3("hello").toString()); | |
// Output: | |
// SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 | |
// Keccak-256: 1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 | |
// RIPEMD-160: 108f07b8382412612c048d07d13f814118445acd | |
// Keccak-512: 52fa80662e64c128f8389c9ea6c73d4c02368004bf4463491900d11aaadca39d47de1b01361f207c512cfa79f0f92c3395c67ff7928e3f5ce3e3c852b392f976 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const cryptoJS = require("crypto-js"); | |
console.log("HMAC-SHA-256:", | |
cryptoJS.HmacSHA256("hello", "key").toString()); | |
// Output: | |
// HMAC-SHA-256: 9307b3b915efb5171ff14d8cb55fbcc798c6c0ef1456d66ded1a6aa723a58b7b |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const cryptoJS = require("crypto-js"); | |
const kdfParams = { keySize: 128 / 32, | |
hasher: cryptoJS.algo.SHA256, iterations: 1000 }; | |
console.log("PBKDF2 (128-bit):", | |
cryptoJS.PBKDF2("password", "salt", kdfParams).toString()); | |
// Output: | |
// PBKDF2 (128-bit): 632c2812e46d4604102ba7618e9d6d7d |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const scrypt = require("scrypt-async"); | |
scrypt('password', 'salt', { | |
N: 16384, // iterations | |
r: 8, // block size | |
p: 1, // parallelism | |
dkLen: 16, // 128-bit key | |
encoding: 'hex' | |
}, console.log); | |
// Output: | |
// 745731af4484f323968969eda289aeee |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment