Skip to content

Instantly share code, notes, and snippets.

@steverogers180
Forked from btxtiger/AesUtil.ts
Last active December 20, 2023 14:52
Show Gist options
  • Save steverogers180/65670917473c196e631418c97b4bef21 to your computer and use it in GitHub Desktop.
Save steverogers180/65670917473c196e631418c97b4bef21 to your computer and use it in GitHub Desktop.
Node.js - AES Encryption/Decryption with AES-256-GCM using random Initialization Vector + Salt
/**
* Cryptography Functions
*
* Forked from btxtiger/AesUtil.ts
* https://gist.github.com/btxtiger/e8eaee70d6e46729d127f1e384e755d6
*/
import crypto from 'crypto';
import { Password } from './types';
/**
* encryption/decryption algorithm
*/
const ALGORITHM = 'aes-256-gcm'
/**
* Derive 256 bit encryption key from password, using salt and iterations -> 32 bytes
* @param password
* @param salt
* @param iterations
*/
function deriveKeyFromPassword(password: Password, salt: Buffer, iterations: number) {
return crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha512');
}
/**
* Encrypt AES 256 GCM
* @param plainText
* @param password
*/
export function encryptAesGcm(plainText: string | object, password: Password) {
try {
if (typeof plainText === 'object') {
plainText = JSON.stringify(plainText);
}
// Generate random salt -> 64 bytes
const salt = crypto.randomBytes(64);
// Generate random initialization vector -> 16 bytes
const iv = crypto.randomBytes(16);
// Generate random count of iterations between 10.000 - 99.999 -> 5 bytes
const iterations = Math.floor(Math.random() * (99999 - 10000 + 1)) + 10000;
// Derive encryption key
const encryptionKey = deriveKeyFromPassword(password, salt, Math.floor(iterations * 0.47 + 1337));
// Create cipher
const cipher = crypto.createCipheriv(ALGORITHM, encryptionKey, iv);
// Update the cipher with data to be encrypted and close cipher
const encryptedData = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
// Get authTag from cipher for decryption // 16 bytes
const authTag = cipher.getAuthTag();
// Join all data into single string, include requirements for decryption
const output = Buffer.concat([salt, iv, authTag, Buffer.from(iterations.toString()), encryptedData]).toString('hex');
return output;
} catch (error) {
console.error("Encryption failed!", error);
return
}
}
/**
* Decrypt AES 256 GCM
* @param cipherText
* @param password
*/
export function decryptAesGcm(cipherText: string, password: Password) {
try {
const inputData = Buffer.from(cipherText, 'hex');
// Split cipherText into partials
const salt = inputData.subarray(0, 64);
const iv = inputData.subarray(64, 80);
const authTag = inputData.subarray(80, 96);
const iterations = parseInt(inputData.subarray(96, 101).toString('utf-8'), 10);
const encryptedData = inputData.subarray(101);
// Derive key
const decryptionKey = deriveKeyFromPassword(password, salt, Math.floor(iterations * 0.47 + 1337));
// Create decipher
const decipher = crypto.createDecipheriv(ALGORITHM, decryptionKey, iv);
decipher.setAuthTag(authTag);
// Decrypt data
const decrypted = decipher.update(encryptedData) + decipher.final('utf-8');
try {
return JSON.parse(decrypted);
} catch (error) {
return decrypted;
}
} catch (error) {
console.error("Decryption failed!", error);
return
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment