Add hmac to the file data
https://stackoverflow.com/questions/10279403/confused-how-to-use-aes-and-hmac https://en.wikipedia.org/wiki/Padding_oracle_attack part of #579
This commit is contained in:
+30
-12
@@ -260,20 +260,23 @@ function decryptFilePath(filePath, backupConfig) {
|
||||
}
|
||||
|
||||
class EncryptStream extends TransformStream {
|
||||
constructor(key) {
|
||||
constructor(encryption) {
|
||||
super();
|
||||
this._ivPushed = false;
|
||||
this._iv = crypto.randomBytes(16);
|
||||
this._cipher = crypto.createCipheriv('aes-256-cbc', key, this._iv);
|
||||
this._cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(encryption.dataKey, 'hex'), this._iv);
|
||||
this._hmac = crypto.createHmac('sha256', Buffer.from(encryption.dataHmacKey, 'hex'));
|
||||
}
|
||||
|
||||
_transform(chunk, ignoredEncoding, callback) {
|
||||
if (!this._ivPushed) {
|
||||
this.push(this._iv);
|
||||
this._hmac.update(this._iv);
|
||||
this._ivPushed = true;
|
||||
}
|
||||
try {
|
||||
const crypt = this._cipher.update(chunk);
|
||||
this._hmac.update(crypt);
|
||||
callback(null, crypt);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
@@ -283,7 +286,9 @@ class EncryptStream extends TransformStream {
|
||||
_flush(callback) {
|
||||
try {
|
||||
const crypt = this._cipher.final();
|
||||
callback(null, crypt);
|
||||
this.push(crypt);
|
||||
this._hmac.update(crypt);
|
||||
callback(null, this._hmac.digest());
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
@@ -291,25 +296,34 @@ class EncryptStream extends TransformStream {
|
||||
}
|
||||
|
||||
class DecryptStream extends TransformStream {
|
||||
constructor(key) {
|
||||
constructor(encryption) {
|
||||
super();
|
||||
this._key = key;
|
||||
this._key = Buffer.from(encryption.dataKey, 'hex');
|
||||
this._iv = Buffer.alloc(0);
|
||||
this._decipher = null;
|
||||
this._hmac = crypto.createHmac('sha256', Buffer.from(encryption.dataHmacKey, 'hex'));
|
||||
this._buffer = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_transform(chunk, ignoredEncoding, callback) {
|
||||
const needed = 16 - this._iv.length;
|
||||
|
||||
if (this._iv.length !== 16) { // not gotten IV yet
|
||||
const needed = 16 - this._iv.length;
|
||||
this._iv = Buffer.concat([this._iv, chunk.slice(0, needed)]);
|
||||
if (this._iv.length !== 16) return callback();
|
||||
|
||||
this._decipher = crypto.createDecipheriv('aes-256-cbc', this._key, this._iv);
|
||||
chunk = chunk.slice(needed);
|
||||
this._hmac.update(this._iv);
|
||||
}
|
||||
|
||||
this._buffer = Buffer.concat([ this._buffer, chunk.slice(needed) ]);
|
||||
if (this._buffer.length < 32) return callback();
|
||||
|
||||
try {
|
||||
const plainText = this._decipher.update(chunk);
|
||||
const cipherText = this._buffer.slice(0, -32);
|
||||
this._hmac.update(cipherText);
|
||||
const plainText = this._decipher.update(cipherText);
|
||||
this._buffer = this._buffer.slice(-32);
|
||||
callback(null, plainText);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
@@ -317,7 +331,11 @@ class DecryptStream extends TransformStream {
|
||||
}
|
||||
|
||||
_flush (callback) {
|
||||
if (this._buffer.length !== 32) return callback(new BoxError(BoxError.CRYPTO_ERROR, 'Invalid password or tampered file (not enough data)'));
|
||||
|
||||
try {
|
||||
if (!this._hmac.digest().equals(this._buffer)) return callback(new BoxError(BoxError.CRYPTO_ERROR, 'Invalid password or tampered file (mac mismatch)'));
|
||||
|
||||
const plainText = this._decipher.final();
|
||||
callback(null, plainText);
|
||||
} catch (error) {
|
||||
@@ -339,7 +357,7 @@ function createReadStream(sourceFile, backupConfig) {
|
||||
});
|
||||
|
||||
if (backupConfig.encryption) {
|
||||
let encryptStream = new EncryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex'));
|
||||
let encryptStream = new EncryptStream(backupConfig.encryption);
|
||||
|
||||
encryptStream.on('error', function (error) {
|
||||
debug('createReadStream: encrypt stream error.', error);
|
||||
@@ -365,7 +383,7 @@ function createWriteStream(destFile, backupConfig) {
|
||||
});
|
||||
|
||||
if (backupConfig.encryption) {
|
||||
let decrypt = new DecryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex'));
|
||||
let decrypt = new DecryptStream(backupConfig.encryption);
|
||||
decrypt.on('error', function (error) {
|
||||
debug('createWriteStream: decrypt stream error.', error);
|
||||
ps.emit('error', new BoxError(BoxError.CRYPTO_ERROR, error.message));
|
||||
@@ -415,7 +433,7 @@ function tarPack(dataLayout, backupConfig, callback) {
|
||||
});
|
||||
|
||||
if (backupConfig.encryption) {
|
||||
const encryptStream = new EncryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex'));
|
||||
const encryptStream = new EncryptStream(backupConfig.encryption);
|
||||
encryptStream.on('error', function (error) {
|
||||
debug('tarPack: encrypt stream error.', error);
|
||||
ps.emit('error', new BoxError(BoxError.EXTERNAL_ERROR, error.message));
|
||||
@@ -626,7 +644,7 @@ function tarExtract(inStream, dataLayout, backupConfig, callback) {
|
||||
});
|
||||
|
||||
if (backupConfig.encryption) {
|
||||
let decrypt = new DecryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex'));
|
||||
let decrypt = new DecryptStream(backupConfig.encryption);
|
||||
decrypt.on('error', function (error) {
|
||||
debug('tarExtract: decrypt stream error.', error);
|
||||
emitError(new BoxError(BoxError.EXTERNAL_ERROR, `Failed to decrypt: ${error.message}`));
|
||||
|
||||
Reference in New Issue
Block a user