Skip to content

Instantly share code, notes, and snippets.

@nakov
Last active November 9, 2023 02:32
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nakov/5bfce93736468884a53e0ab2be06641e to your computer and use it in GitHub Desktop.
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
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
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
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
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
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
const cryptoJS = require("crypto-js");
console.log("HMAC-SHA-256:",
cryptoJS.HmacSHA256("hello", "key").toString());
// Output:
// HMAC-SHA-256: 9307b3b915efb5171ff14d8cb55fbcc798c6c0ef1456d66ded1a6aa723a58b7b
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
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