Last active
March 21, 2022 07:35
-
-
Save lauslim12/4ec0552c8e3bbb690f4e527ccf49ab72 to your computer and use it in GitHub Desktop.
TypeScript way to implement AES-256-GCM encryption in an object-oriented way in Node.js. Complete with code comments and sample runner.
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
/** | |
* You need Node.js, TypeScript, and NanoID. Use `npm i nanoid` in a sample project to test things out! | |
* Steps: | |
* - Create a folder, for example `mkdir aes-test` then `cd aes-test`. | |
* - Copy this script and create a file, for example `index.ts`. | |
* - Paste the file there. | |
* - `npm i nanoid ts-node-dev`. | |
* - `npm run ts-node-dev .`. | |
* - Profit! | |
*/ | |
import { nanoid } from 'nanoid'; | |
import crypto from 'node:crypto'; | |
/** | |
* Handles AES-256-GCM operations, such as string encryption, string | |
* decryption, IV generation, and secure secret generation. | |
*/ | |
class AES256GCM { | |
/** | |
* Default algorithm for the encryption is AES-256-GCM. | |
*/ | |
public algorithm = 'aes-256-gcm' as const; | |
/** | |
* Empty constructor, we do not actually need anything. | |
*/ | |
constructor() {} | |
/** | |
* Generates a secure IV for AES. In GCM, IV has to be | |
* unique for every data to prevent forgery attacks. | |
* | |
* @returns 128 bits random IV. | |
*/ | |
generateIV() { | |
return Buffer.from(nanoid(16), 'utf-8'); | |
} | |
/** | |
* Generates a random 256 bits secret. | |
* | |
* @returns 256 bits random secret. | |
*/ | |
generateSecret() { | |
return nanoid(32); | |
} | |
/** | |
* Encrypts a plaintext using a secret as its key using the AES-256 Galois Counter Mode. | |
* | |
* @param plaintext - Plaintext to be encrypted. | |
* @param secret - Secret to be used as a key. | |
* @returns An object returning the ciphertext, IV and the authentication tag in Base64 | |
* format. | |
*/ | |
encrypt(plaintext: string, secret: string) { | |
// Generate a secure IV and create a basic cipher. | |
const iv = this.generateIV(); | |
const cipher = crypto.createCipheriv(this.algorithm, secret, iv); | |
// Update cipher. | |
const data = cipher.update(plaintext, 'utf-8', 'base64'); | |
const final = cipher.final('base64'); | |
return { | |
ciphertext: `${data}${final}`, | |
iv: iv.toString('base64'), | |
tag: cipher.getAuthTag().toString('base64'), | |
}; | |
} | |
/** | |
* Decrypts a ciphertext using a secret, an IV, and an authentication tag. | |
* | |
* @param ciphertext - Ciphertext to be fed to the input. | |
* @param secret - Key/secret to be used. | |
* @param iv - IV returned from the previous encryption process. Has to be in Base64. | |
* @param tag - Authentication tag returned from the previous encryption process. Has to be in Base64. | |
* @returns The string, decrypted in plaintext format (UTF-8). | |
*/ | |
decrypt(ciphertext: string, secret: string, iv: string, tag: string) { | |
// Attempt to decipher with IV, secret, and authentication tag. | |
const decipher = crypto | |
.createDecipheriv(this.algorithm, secret, Buffer.from(iv, 'base64')) | |
.setAuthTag(Buffer.from(tag, 'base64')); | |
// Updates the previously created cipher with the ciphertext. | |
const data = decipher.update(ciphertext, 'base64', 'utf-8'); | |
// Returns the decrypted ciphertext. | |
return `${data}${decipher.final('utf-8')}`; | |
} | |
} | |
/** | |
* Driver code. | |
*/ | |
function main() { | |
// Initialize parameters. | |
const AES = new AES256GCM(); | |
const text = 'Hello, World! This is AES-256 GCM with Node.js!'; | |
const secret = AES.generateSecret(); | |
// Perform encryption and decryption. | |
const encrypted = AES.encrypt(text, secret); | |
const decrypted = AES.decrypt( | |
encrypted.ciphertext, | |
secret, | |
encrypted.iv, | |
encrypted.tag | |
); | |
// Match results. | |
console.log(`Plaintext: ${text}.`); | |
console.log(`Ciphertext (Base64): ${encrypted.ciphertext}.`); | |
console.log(`IV (Base64): ${encrypted.iv}.`); | |
console.log(`Authentication tag (Base64): ${encrypted.tag}.`); | |
console.log(`Decrypted text: ${decrypted}.`); | |
console.log(`Plaintext equals decrypted text: ${text === decrypted}.`); | |
} | |
/** | |
* Run program. | |
*/ | |
void main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment