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.
120 lines (119 loc) • 6.21 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChaCha20Poly1305EncryptionConfig = exports.ChaCha20Poly1305Encryption = void 0;
const crypto_1 = require("crypto");
const encodingUtils_1 = require("../utils/encodingUtils");
const DEFAULT_TAG_LENGTH = 16;
const DEFAULT_KEY_LENGTH = 32;
const DEFAULT_ITERATIONS = 100000;
const DEFAULT_NONCE_LENGTH = 12;
class ChaCha20Poly1305Encryption {
async encryptText(plaintext, config, encoding) {
const { password, textEncoding = 'utf-8' } = config;
// Generate random salt and nonce for each encryption operation
const salt = Buffer.from(crypto.getRandomValues(new Uint8Array(16)));
const nonce = Buffer.from(crypto.getRandomValues(new Uint8Array(ChaCha20Poly1305Encryption.NONCE_LENGTH)));
const key = this.deriveKey(password, salt);
const textBuffer = (0, encodingUtils_1.encodeText)(plaintext, encoding !== null && encoding !== void 0 ? encoding : textEncoding);
const cipher = (0, crypto_1.createCipheriv)('chacha20-poly1305', key, nonce, {
authTagLength: ChaCha20Poly1305Encryption.TAG_LENGTH,
});
const encrypted = Buffer.concat([
cipher.update(Buffer.from(textBuffer)),
cipher.final()
]);
const tag = cipher.getAuthTag();
// Package salt + nonce + encrypted + tag together
const finalBuffer = Buffer.concat([
salt,
nonce,
encrypted,
tag,
]);
return { data: finalBuffer.buffer.slice(finalBuffer.byteOffset, finalBuffer.byteOffset + finalBuffer.byteLength) };
}
async decryptText(encryptedData, config, encoding) {
const { password, textEncoding = 'utf-8' } = config;
// Parse salt + nonce + ciphertext + tag from the packaged data
const data = Buffer.from(encryptedData);
const salt = data.subarray(0, 16);
const nonce = data.subarray(16, 16 + ChaCha20Poly1305Encryption.NONCE_LENGTH);
const encryptedWithTag = data.subarray(16 + ChaCha20Poly1305Encryption.NONCE_LENGTH);
const tag = encryptedWithTag.subarray(encryptedWithTag.length - ChaCha20Poly1305Encryption.TAG_LENGTH);
const ciphertext = encryptedWithTag.subarray(0, encryptedWithTag.length - ChaCha20Poly1305Encryption.TAG_LENGTH);
const key = this.deriveKey(password, salt);
const decipher = (0, crypto_1.createDecipheriv)('chacha20-poly1305', key, nonce, {
authTagLength: ChaCha20Poly1305Encryption.TAG_LENGTH,
});
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
return (0, encodingUtils_1.decodeText)(decrypted.buffer.slice(decrypted.byteOffset, decrypted.byteOffset + decrypted.byteLength), encoding !== null && encoding !== void 0 ? encoding : textEncoding);
}
async encryptFile(fileBuffer, config) {
const { password } = config;
// Generate random salt and nonce for each encryption operation
const salt = Buffer.from(crypto.getRandomValues(new Uint8Array(16)));
const nonce = Buffer.from(crypto.getRandomValues(new Uint8Array(ChaCha20Poly1305Encryption.NONCE_LENGTH)));
const key = this.deriveKey(password, salt);
const dataBuffer = Buffer.from(fileBuffer);
const cipher = (0, crypto_1.createCipheriv)('chacha20-poly1305', key, nonce, {
authTagLength: ChaCha20Poly1305Encryption.TAG_LENGTH,
});
const encrypted = Buffer.concat([
cipher.update(dataBuffer),
cipher.final()
]);
const tag = cipher.getAuthTag();
// Package salt + nonce + encrypted + tag together
const finalBuffer = Buffer.concat([
salt,
nonce,
encrypted,
tag,
]);
return { data: finalBuffer.buffer.slice(finalBuffer.byteOffset, finalBuffer.byteOffset + finalBuffer.byteLength) };
}
async decryptFile(encryptedBuffer, config) {
const { password } = config;
// Parse salt + nonce + ciphertext + tag from the packaged data
const data = Buffer.from(encryptedBuffer);
const salt = data.subarray(0, 16);
const nonce = data.subarray(16, 16 + ChaCha20Poly1305Encryption.NONCE_LENGTH);
const encryptedWithTag = data.subarray(16 + ChaCha20Poly1305Encryption.NONCE_LENGTH);
const tag = encryptedWithTag.subarray(encryptedWithTag.length - ChaCha20Poly1305Encryption.TAG_LENGTH);
const ciphertext = encryptedWithTag.subarray(0, encryptedWithTag.length - ChaCha20Poly1305Encryption.TAG_LENGTH);
const key = this.deriveKey(password, salt);
const decipher = (0, crypto_1.createDecipheriv)('chacha20-poly1305', key, nonce, {
authTagLength: ChaCha20Poly1305Encryption.TAG_LENGTH,
});
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
return decrypted.buffer.slice(decrypted.byteOffset, decrypted.byteOffset + decrypted.byteLength);
}
deriveKey(password, salt) {
return (0, crypto_1.pbkdf2Sync)(password, salt, ChaCha20Poly1305Encryption.ITERATIONS, ChaCha20Poly1305Encryption.KEY_LENGTH, 'sha256');
}
validateNonce(nonce) {
if (nonce.length !== ChaCha20Poly1305Encryption.NONCE_LENGTH) {
throw new Error(`Nonce must be ${ChaCha20Poly1305Encryption.NONCE_LENGTH} bytes`);
}
}
}
exports.ChaCha20Poly1305Encryption = ChaCha20Poly1305Encryption;
ChaCha20Poly1305Encryption.TAG_LENGTH = DEFAULT_TAG_LENGTH;
ChaCha20Poly1305Encryption.KEY_LENGTH = DEFAULT_KEY_LENGTH;
ChaCha20Poly1305Encryption.ITERATIONS = DEFAULT_ITERATIONS;
ChaCha20Poly1305Encryption.NONCE_LENGTH = DEFAULT_NONCE_LENGTH;
class ChaCha20Poly1305EncryptionConfig {
constructor(password, textEncoding) {
this.password = password;
this.textEncoding = textEncoding !== null && textEncoding !== void 0 ? textEncoding : 'utf-8';
}
}
exports.ChaCha20Poly1305EncryptionConfig = ChaCha20Poly1305EncryptionConfig;