easy-cipher-mate
Version:
A CLI and programmatic tool for encryption/decryption supporting AES-GCM and ChaCha20-Poly1305 algorithms, with text encoding options and line-by-line text file processing.
117 lines (116 loc) • 5.74 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AESGCMEncryptionConfig = exports.AESGCMEncryption = void 0;
const encodingUtils_1 = require("../utils/encodingUtils");
/**
* AES-GCM encryption implementation with automatic salt and IV generation.
*
* @remarks
* This class provides secure AES-GCM encryption with the following security features:
* - Automatic random salt generation (16 bytes) for each encryption
* - Automatic random IV generation (12 bytes) for each encryption
* - PBKDF2 key derivation with 100,000 iterations
* - Authenticated encryption with built-in integrity protection
* - Secure data format: [salt | iv | ciphertext]
*
* @example
* ```typescript
* const encryption = new AESGCMEncryption();
* const config = new AESGCMEncryptionConfig('my-password');
*
* const encrypted = await encryption.encryptText('Hello World', config);
* const decrypted = await encryption.decryptText(encrypted.data, config);
* ```
*/
class AESGCMEncryption {
/**
* Encrypts a plaintext string using AES-GCM with automatic salt and IV generation.
*
* @param plaintext - The string to encrypt
* @param configuration - The encryption configuration containing password and encoding
* @param encoding - Optional text encoding override
* @returns Promise resolving to encrypted data in format: [salt | iv | ciphertext]
*
* @remarks
* Each call generates new random salt and IV, ensuring unique ciphertext even for identical plaintext.
* The output format is: 16-byte salt + 12-byte IV + AES-GCM ciphertext (includes auth tag).
*/
async encryptText(plaintext, configuration, encoding) {
const { password, textEncoding = 'utf-8' } = configuration;
// Generate random salt and IV for each encryption operation
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(password, salt);
const data = (0, encodingUtils_1.encodeText)(plaintext, encoding !== null && encoding !== void 0 ? encoding : textEncoding);
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
// Package salt + iv + ciphertext together
const saltBuffer = salt.buffer;
const ivBuffer = iv.buffer;
const result = new ArrayBuffer(saltBuffer.byteLength + ivBuffer.byteLength + ciphertext.byteLength);
const resultView = new Uint8Array(result);
resultView.set(new Uint8Array(saltBuffer), 0);
resultView.set(new Uint8Array(ivBuffer), saltBuffer.byteLength);
resultView.set(new Uint8Array(ciphertext), saltBuffer.byteLength + ivBuffer.byteLength);
return {
data: result,
};
}
async decryptText(encryptedData, configuration, encoding) {
const { password, textEncoding = 'utf-8' } = configuration;
// Parse salt + iv + ciphertext from the packaged data
const dataView = new Uint8Array(encryptedData);
const salt = dataView.slice(0, 16);
const iv = dataView.slice(16, 28);
const ciphertext = dataView.slice(28);
const key = await deriveKey(password, salt);
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
return (0, encodingUtils_1.decodeText)(decrypted, encoding !== null && encoding !== void 0 ? encoding : textEncoding);
}
async encryptFile(fileBuffer, configuration) {
const { password } = configuration;
// Generate random salt and IV for each encryption operation
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(password, salt);
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, fileBuffer);
// Package salt + iv + ciphertext together
const saltBuffer = salt.buffer;
const ivBuffer = iv.buffer;
const result = new ArrayBuffer(saltBuffer.byteLength + ivBuffer.byteLength + ciphertext.byteLength);
const resultView = new Uint8Array(result);
resultView.set(new Uint8Array(saltBuffer), 0);
resultView.set(new Uint8Array(ivBuffer), saltBuffer.byteLength);
resultView.set(new Uint8Array(ciphertext), saltBuffer.byteLength + ivBuffer.byteLength);
return {
data: result,
};
}
async decryptFile(encryptedBuffer, configuration) {
const { password } = configuration;
// Parse salt + iv + ciphertext from the packaged data
const dataView = new Uint8Array(encryptedBuffer);
const salt = dataView.slice(0, 16);
const iv = dataView.slice(16, 28);
const ciphertext = dataView.slice(28);
const key = await deriveKey(password, salt);
return await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
}
}
exports.AESGCMEncryption = AESGCMEncryption;
class AESGCMEncryptionConfig {
constructor(password, textEncoding) {
this.password = password;
this.textEncoding = textEncoding !== null && textEncoding !== void 0 ? textEncoding : 'utf-8';
}
}
exports.AESGCMEncryptionConfig = AESGCMEncryptionConfig;
async function deriveKey(password, salt) {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveKey"]);
return crypto.subtle.deriveKey({
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256"
}, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
}