node-aescrypt
Version:
A node implementation of the AES Crypt <https://www.aescrypt.com/> file encryption format.
207 lines • 16.4 kB
JavaScript
import { createDecipheriv } from 'crypto';
import { Transform } from 'stream';
import { AESCRYPT_FILE_FORMAT_VERSION, getHMAC, getKey, toStream, withStream, } from './util';
/**
* Decrypt a Buffer that is in the AES Crypt file format.
*
* Create a stream transformer that takes [Readable stream](https://nodejs.org/api/stream.html)
* that is encrypted in the
* [AES Crypt file format](https://www.aescrypt.com/aes_file_format.html) and
* decrypts it passing it on as a Readable stream.
*/
export class Decrypt extends Transform {
constructor(password, options) {
super(options);
this.password = password;
this.decipher = null;
this.hmac = null;
this.mode = 0;
this.buffer = Buffer.alloc(0);
}
static get MODE_FILE_HEADER() {
return 0;
}
static get MODE_EXTESIONS() {
return 1;
}
static get MODE_CREDENTIALS() {
return 2;
}
static get MODE_DECRYPT() {
return 3;
}
// Create a small helper static method if you just want to decrypt a whole
// Buffer all at once.
static buffer(password, buffer) {
return new Promise((resolve, reject) => {
toStream(buffer)
.pipe(new Decrypt(password))
.pipe(withStream(contents => {
resolve(contents);
}))
.on('error', reject);
});
}
_transform(chunk, _, callback) {
this.buffer = Buffer.concat([this.buffer, chunk]);
let error = null;
// Move through the various sections of the file format and raise an error
// If anything is malformed.
if (this.mode === Decrypt.MODE_FILE_HEADER) {
error = this._modeFileHeader();
}
if (!error && this.mode === Decrypt.MODE_EXTESIONS) {
error = this._modeExtensions();
}
if (!error && this.mode === Decrypt.MODE_CREDENTIALS) {
error = this._modeCredentials();
}
// Finally ready to decrypt the contents.
if (!error && this.mode === Decrypt.MODE_DECRYPT) {
// We need to reserve 33 bytes (+ 16 for the padding) of the end for the file-size-modulo-16 and HMAC.
if (this.buffer.length > 49) {
const encChunk = this.buffer.slice(0, -49);
// This is unnecessary, but makes tslint keep quiet.
if (this.decipher == null) {
return;
}
if (this.hmac == null) {
return;
}
this.hmac.update(encChunk);
this.push(this.decipher.update(encChunk));
this.buffer = this.buffer.slice(-49);
}
}
if (error) {
callback(error);
}
else {
callback();
}
}
_flush(callback) {
let error = null;
// If we never got to the decryption mode, something went terribly wrong.
// Most likely, there is a problem in the extensions, and we never found
// the end.
if (this.mode !== Decrypt.MODE_DECRYPT) {
error = new Error('Error: Message has been altered or password is incorrect');
}
else {
// We are at the end of the file. Let's hash that last remaining cipher text
// and check the HMAC.
const encChunk = this.buffer.slice(0, 16);
const lenMod16 = this.buffer.readUInt8(16);
const encHMACActual = this.buffer.slice(17);
// This is unnecessary, but makes tslint keep quiet.
if (this.decipher == null) {
return;
}
if (this.hmac == null) {
return;
}
this.hmac.update(encChunk);
const encHMACExpected = this.hmac.digest();
// Validately the integrity of the cipher text.
if (encHMACExpected.compare(encHMACActual) !== 0) {
error = new Error('Error: Message has been altered or password is incorrect');
}
// Validate the padding length (or more accurately, the length of the last block minus the padding).
else if (lenMod16 > 16) {
error = new Error('Error: Message has been altered or password is incorrect');
}
else {
// Decrypt the last block and send it on its way.
const decChunk = Buffer.concat([
this.decipher.update(encChunk),
this.decipher.final(),
]).slice(0, lenMod16);
this.push(decChunk);
}
}
if (error) {
callback(error);
}
else {
callback();
}
}
_modeFileHeader() {
if (this.buffer.length >= 5) {
const type = this.buffer.slice(0, 3).toString();
const version = this.buffer.readUInt8(3);
if (type !== 'AES') {
return new Error('Error: Bad file header (not aescrypt file or is corrupted?');
}
// We only understand the version 2 of the AES Crypt file format as described
// at https://www.aescrypt.com/aes_file_format.html.
if (version !== AESCRYPT_FILE_FORMAT_VERSION) {
return new Error('Error: Unsupported AES file version');
}
this.buffer = this.buffer.slice(5);
this.mode = Decrypt.MODE_EXTESIONS;
}
return null;
}
_modeExtensions() {
let i = 0;
// Search through the buffer to the length.
// If we can't find the end of the extensions in the current buffer, let it
// buffer a little more.
while (i + 1 < this.buffer.length) {
const extLen = this.buffer.readUInt16BE(i);
i += 2;
// If this extension has a length, fast-forward past it.
if (extLen > 0) {
i += extLen;
}
// If this is a zero length extension, we are done.
else {
this.buffer = this.buffer.slice(i);
this.mode = Decrypt.MODE_CREDENTIALS;
break;
}
}
return null;
}
_modeCredentials() {
if (this.buffer.length >= 96) {
const credIV = this.buffer.slice(0, 16);
const credKey = getKey(credIV, this.password);
const credDecipher = this._getDecipher(credKey, credIV);
credDecipher.setAutoPadding(false);
const credBlock = this.buffer.slice(16, 64);
const credHMACActual = this.buffer.slice(64, 96);
const credHMACExpected = getHMAC(credKey)
.update(credBlock)
.digest();
// First we check the HMAC signature of the encrypted credentials block.
// This ensures nothing was tampered with. It also has the added benefit
// of checking the password early on in the decryption process.
if (credHMACExpected.compare(credHMACActual) !== 0) {
return new Error('Error: Message has been altered or password is incorrect');
}
// Decrypt the credentials we need for the rest of the contents.
const decryptedCredBlock = Buffer.concat([
credDecipher.update(credBlock),
credDecipher.final(),
]);
const encIV = decryptedCredBlock.slice(0, 16);
const encKey = decryptedCredBlock.slice(16, 48);
// Create our main workhorses using the decrypted credentials.
this.decipher = this._getDecipher(encKey, encIV);
this.hmac = getHMAC(encKey);
delete this.password; // Don't need this anymore.
this.buffer = this.buffer.slice(96);
this.mode = Decrypt.MODE_DECRYPT;
}
return null;
}
_getDecipher(key, iv) {
const encDecipher = createDecipheriv('aes-256-cbc', key, iv);
encDecipher.setAutoPadding(false);
return encDecipher;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVjcnlwdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvZGVjcnlwdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsZ0JBQWdCLEVBQWtCLE1BQU0sUUFBUSxDQUFDO0FBQzFELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDbkMsT0FBTyxFQUNMLDRCQUE0QixFQUM1QixPQUFPLEVBQ1AsTUFBTSxFQUNOLFFBQVEsRUFFUixVQUFVLEdBQ1gsTUFBTSxRQUFRLENBQUM7QUFFaEI7Ozs7Ozs7R0FPRztBQUNILE1BQU0sT0FBTyxPQUFRLFNBQVEsU0FBUztJQWtDcEMsWUFBWSxRQUFnQixFQUFFLE9BQWE7UUFDekMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2YsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7UUFDekIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7UUFDckIsSUFBSSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDakIsSUFBSSxDQUFDLElBQUksR0FBRyxDQUFDLENBQUM7UUFDZCxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDaEMsQ0FBQztJQXhDRCxNQUFNLEtBQUssZ0JBQWdCO1FBQ3pCLE9BQU8sQ0FBQyxDQUFDO0lBQ1gsQ0FBQztJQUNELE1BQU0sS0FBSyxjQUFjO1FBQ3ZCLE9BQU8sQ0FBQyxDQUFDO0lBQ1gsQ0FBQztJQUNELE1BQU0sS0FBSyxnQkFBZ0I7UUFDekIsT0FBTyxDQUFDLENBQUM7SUFDWCxDQUFDO0lBQ0QsTUFBTSxLQUFLLFlBQVk7UUFDckIsT0FBTyxDQUFDLENBQUM7SUFDWCxDQUFDO0lBQ0QsMEVBQTBFO0lBQzFFLHNCQUFzQjtJQUNmLE1BQU0sQ0FBQyxNQUFNLENBQUMsUUFBZ0IsRUFBRSxNQUFjO1FBQ25ELE9BQU8sSUFBSSxPQUFPLENB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