Skip to content

Instantly share code, notes, and snippets.

@btxtiger
Forked from AndiDittrich/AesUtil.js
Last active April 25, 2024 08:10
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save btxtiger/e8eaee70d6e46729d127f1e384e755d6 to your computer and use it in GitHub Desktop.
Save btxtiger/e8eaee70d6e46729d127f1e384e755d6 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 AndiDittrich/AesUtil.js
* https://gist.github.com/AndiDittrich/4629e7db04819244e843
*/
import crypto, { CipherGCM, CipherGCMTypes, DecipherGCM } from 'crypto';
import { Password } from './types';
/**
* Get encryption/decryption algorithm
*/
function getAlgorithm(): CipherGCMTypes {
return 'aes-256-gcm';
}
/**
* Get encrypted string prefix
*/
function getEncryptedPrefix(): string {
return 'enc::';
}
/**
* 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): Buffer {
return crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha512');
}
/**
* Encrypt AES 256 GCM
* @param plainText
* @param password
*/
export function encryptAesGcm(plainText: string | object, password: Password): string | undefined {
try {
if (typeof plainText === 'object') {
plainText = JSON.stringify(plainText);
} else {
plainText = String(plainText);
}
const algorithm: CipherGCMTypes = getAlgorithm();
// 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
// @ts-ignore: TS expects the wrong createCipher return type here
const cipher: CipherGCM = 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 getEncryptedPrefix() + output;
} catch (error) {
console.error('Encryption failed!');
console.error(error);
return void 0;
}
}
/**
* Decrypt AES 256 GCM
* @param cipherText
* @param password
*/
export function decryptAesGcm(cipherText: string, password: Password): string | undefined {
try {
const algorithm: CipherGCMTypes = getAlgorithm();
const cipherTextParts = cipherText.split(getEncryptedPrefix());
// If it's not encrypted by this, reject with undefined
if (cipherTextParts.length !== 2) {
console.error('Could not determine the beginning of the cipherText. Maybe not encrypted by this method.');
return void 0;
} else {
cipherText = cipherTextParts[1];
}
const inputData: Buffer = Buffer.from(cipherText, 'hex');
// Split cipherText into partials
const salt: Buffer = inputData.slice(0, 64);
const iv: Buffer = inputData.slice(64, 80);
const authTag: Buffer = inputData.slice(80, 96);
const iterations: number = parseInt(inputData.slice(96, 101).toString('utf-8'), 10);
const encryptedData: Buffer = inputData.slice(101);
// Derive key
const decryptionKey = deriveKeyFromPassword(password, salt, Math.floor(iterations * 0.47 + 1337));
// Create decipher
// @ts-ignore: TS expects the wrong createDecipher return type here
const decipher: DecipherGCM = crypto.createDecipheriv(algorithm, decryptionKey, iv);
decipher.setAuthTag(authTag);
// Decrypt data
// @ts-ignore: TS expects the wrong createDecipher return type here
const decrypted = decipher.update(encryptedData, 'binary', 'utf-8') + decipher.final('utf-8');
try {
return JSON.parse(decrypted);
} catch (error) {
return decrypted;
}
} catch (error) {
console.error('Decryption failed!');
console.error(error);
return void 0;
}
}
@abbaty48
Copy link

Hi there, i am having some issue here

  1. where did this come from
    import { Password } from './types';
  2. inputData.slice() is deprecated

Please i need this ASAP, thank you, GOD BLESS

@btxtiger
Copy link
Author

@abbaty48

  1. export type Password = string | Buffer | NodeJS.TypedArray | DataView;
  2. nodejs/node#50624 you might use buffer.subarray instead, so inputData.subarray

@abbaty48
Copy link

@btxtiger Thank you, i am on it, i'll let you know

@abbaty48
Copy link

Hi there, I've resolve it, no error but i haven't tested it yet. I'm trying to encrypt a json from request.body api request, the body must be encrypted and as that code shows it might be viable for the encrypting to failed, what do you think i ought to do. thanks you.

@abbaty48
Copy link

@btxtiger Thank you, my man, both the Encryption and Decryption work like a charm

@harshul21
Copy link

const fs = require('fs');
const crypto = require('crypto');

function decryptAES128CBC(encryptedText, key, iv) {
const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key, 'hex'), Buffer.from(iv, 'hex'));

let decrypted = decipher.update(encryptedText, 'hex', 'utf-8');
decrypted += decipher.final('utf-8');
return decrypted;
}

// Example usage:
const encryptedFilePath = 'bg16V5qCkfbkQ4nGZmWhurld0Tj8BArwyTRhafgiNkM=';
const encryptionKey = '0db19bb5d246964ea386c1c85e981264';
const initializationVector = Buffer.alloc(16).fill(0);

try {

const decryptedMessage = decryptAES128CBC(encryptedFilePath, encryptionKey, initializationVector);
console.log('Decrypted Message:', decryptedMessage);
} catch (error) {
console.error('Error:', error.message);
}

I am getting Error: error:1C80006B:Provider routines::wrong final block length
Can anyone help

@btxtiger
Copy link
Author

@harshul21 Please ask your question on Stackoverflow, not here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment