Skip to content

Instantly share code, notes, and snippets.

@bozdoz
Last active February 28, 2024 02:48
Show Gist options
  • Save bozdoz/62b2f42ccb8df91a4e97d1bf9e10a7cd to your computer and use it in GitHub Desktop.
Save bozdoz/62b2f42ccb8df91a4e97d1bf9e10a7cd to your computer and use it in GitHub Desktop.
Node.js Encryption/Decryption Example
const crypto = require("node:crypto");
const { Buffer } = require("node:buffer");
const ALGO = "aes-256-cbc";
// random SECRET stored in memory
const SECRET = crypto.randomBytes(64);
function getKey(salt) {
// In 2023, OWASP recommended to use 600,000 iterations for PBKDF2-HMAC-SHA256 and 210,000 for PBKDF2-HMAC-SHA512.[6]
return crypto.pbkdf2Sync(SECRET, salt, 210_000, 32, "sha512");
}
function encrypt(plaintext) {
// The US National Institute of Standards and Technology recommends a salt length of at least 128 bits
const salt = crypto.randomBytes(128);
const key = getKey(salt);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGO, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
// all fields can be stored together and used for decryption
return {
iv: iv.toString("hex"),
data: encrypted.toString("hex"),
salt: salt.toString("hex"),
};
}
// Warning: could throw Error
function decrypt(encrypted) {
const key = getKey(Buffer.from(encrypted.salt, "hex"));
const iv = Buffer.from(encrypted.iv, "hex");
const encryptedText = Buffer.from(encrypted.data, "hex");
const decipher = crypto.createDecipheriv(ALGO, Buffer.from(key), iv);
const decrypted = Buffer.concat([
decipher.update(encryptedText),
decipher.final(),
]);
return decrypted.toString();
}
const message = encrypt("MONEY");
console.log(message);
console.log(decrypt(message));
import crypto, { CipherGCMTypes } from "node:crypto";
import { Buffer } from "node:buffer";
const ALGO: CipherGCMTypes = "aes-256-gcm";
// random SECRET stored in memory
// aes-256 keys must be 256 bits in length
// https://security.stackexchange.com/questions/266130/generate-aes-256-gcm-key
const SECRET = crypto
.generateKeySync("aes", {
length: 256,
})
.export();
function getKey(salt: Buffer): Promise<Buffer> {
// In 2023, OWASP recommended to use 600,000 iterations for PBKDF2-HMAC-SHA256 and 210,000 for PBKDF2-HMAC-SHA512.[6]
return new Promise((resolve, reject) => {
crypto.pbkdf2(SECRET, salt, 210_000, 32, "sha512", (err, derivedKey) => {
if (err) {
reject(err);
} else {
resolve(derivedKey);
}
});
});
}
interface Encrypted {
iv: string;
data: string;
salt: string;
auth: string;
}
export async function encrypt(plaintext: string): Promise<Encrypted> {
// The US National Institute of Standards and Technology recommends a salt length of at least 128 bits
const salt = crypto.randomBytes(128);
const key = await getKey(salt);
// iv needs to be 12 bytes
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv(ALGO, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
// all fields can be stored together and used for decryption
return {
iv: iv.toString("hex"),
data: encrypted.toString("hex"),
salt: salt.toString("hex"),
auth: cipher.getAuthTag().toString("hex"),
};
}
// Warning: could throw Error
export async function decrypt(encrypted: Encrypted): Promise<string> {
const key = await getKey(Buffer.from(encrypted.salt, "hex"));
const iv = Buffer.from(encrypted.iv, "hex");
const encryptedText = Buffer.from(encrypted.data, "hex");
const decipher = crypto.createDecipheriv(ALGO, key, iv);
decipher.setAuthTag(Buffer.from(encrypted.auth, "hex"));
const decrypted = Buffer.concat([
decipher.update(encryptedText),
decipher.final(),
]);
return decrypted.toString();
}
(async () => {
const message = await encrypt("MONEY");
console.log(message);
console.log(await decrypt(message));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment