From 795e3c57da5d925a732defedac4482c95bdf1e76 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Wed, 20 May 2020 22:27:28 -0700 Subject: [PATCH] Add a header for encrypted backup files this is required to identify old backups and new backups for decryption --- src/backups.js | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/backups.js b/src/backups.js index 95b21087f..114fb2a56 100644 --- a/src/backups.js +++ b/src/backups.js @@ -274,22 +274,25 @@ function decryptFilePath(filePath, encryption) { class EncryptStream extends TransformStream { constructor(encryption) { super(); - this._ivPushed = false; + this._headerPushed = false; this._iv = crypto.randomBytes(16); this._cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(encryption.dataKey, 'hex'), this._iv); this._hmac = crypto.createHmac('sha256', Buffer.from(encryption.dataHmacKey, 'hex')); } - pushIvIfNeeded() { - if (!this._ivPushed) { + pushHeaderIfNeeded() { + if (!this._headerPushed) { + const magic = Buffer.from('CBV2'); + this.push(magic); + this._hmac.update(magic); this.push(this._iv); this._hmac.update(this._iv); - this._ivPushed = true; + this._headerPushed = true; } } _transform(chunk, ignoredEncoding, callback) { - this.pushIvIfNeeded(); + this.pushHeaderIfNeeded(); try { const crypt = this._cipher.update(chunk); @@ -302,7 +305,7 @@ class EncryptStream extends TransformStream { _flush(callback) { try { - this.pushIvIfNeeded(); // for 0-length files + this.pushHeaderIfNeeded(); // for 0-length files const crypt = this._cipher.final(); this.push(crypt); this._hmac.update(crypt); @@ -317,25 +320,28 @@ class DecryptStream extends TransformStream { constructor(encryption) { super(); this._key = Buffer.from(encryption.dataKey, 'hex'); - this._iv = Buffer.alloc(0); + this._header = 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; + const needed = 20 - this._header.length; // 4 for magic, 16 for iv - if (this._iv.length !== 16) { // not gotten IV yet - this._iv = Buffer.concat([this._iv, chunk.slice(0, needed)]); - if (this._iv.length !== 16) return callback(); + if (this._header.length !== 20) { // not gotten header yet + this._header = Buffer.concat([this._header, chunk.slice(0, needed)]); + if (this._header.length !== 20) return callback(); - this._decipher = crypto.createDecipheriv('aes-256-cbc', this._key, this._iv); - this._hmac.update(this._iv); + if (!this._header.slice(0, 4).equals(new Buffer.from('CBV2'))) return callback(new BoxError(BoxError.CRYPTO_ERROR, 'Invalid magic in header')); + + const iv = this._header.slice(4); + this._decipher = crypto.createDecipheriv('aes-256-cbc', this._key, iv); + this._hmac.update(this._header); } this._buffer = Buffer.concat([ this._buffer, chunk.slice(needed) ]); - if (this._buffer.length < 32) return callback(); + if (this._buffer.length < 32) return callback(); // hmac trailer length is 32 try { const cipherText = this._buffer.slice(0, -32);