From 0175e2d0927374cbc7a7ea667bb2240f2f2cf042 Mon Sep 17 00:00:00 2001 From: Georgii Petrov Date: Tue, 19 Mar 2024 10:15:25 +0300 Subject: [PATCH] [bugfix] Fix of AES encoding, test added --- Common/sources/utils.js | 19 +++++++++++-------- tests/unit/utils.tests.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tests/unit/utils.tests.js diff --git a/Common/sources/utils.js b/Common/sources/utils.js index 30905dc8..65ec0a6c 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -1049,8 +1049,10 @@ exports.encryptPassword = async function (ctx, password) { const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, initializationVector); const encryptedData = Buffer.concat([cipher.update(password, 'utf8'), cipher.final()]); const authTag = cipher.getAuthTag(); + const predicate = iterations.toString(16); + const data = Buffer.concat([salt, initializationVector, authTag, encryptedData]).toString('hex'); - return Buffer.concat([salt, initializationVector, authTag, Buffer.from(iterations.toString()), encryptedData]).toString('hex'); + return `${predicate}:${data}`; }; exports.decryptPassword = async function (ctx, password) { const pbkdf2Promise = util.promisify(crypto.pbkdf2); @@ -1060,12 +1062,12 @@ exports.decryptPassword = async function (ctx, password) { keyByteLength = 32, saltByteLength = 64, initializationVectorByteLength = 16, - iterationsByteLength = 5 } = tenAESConfig; - const data = Buffer.from(password, 'hex'); + const [iterations, dataHex] = password.split(':'); + const data = Buffer.from(dataHex, 'hex'); // authTag in node.js equals 16 bytes(128 bits), see https://stackoverflow.com/questions/33976117/does-node-js-crypto-use-fixed-tag-size-with-gcm-mode - const delta = [saltByteLength, initializationVectorByteLength, 16, iterationsByteLength]; + const delta = [saltByteLength, initializationVectorByteLength, 16]; const pointerArray = []; for (let byte = 0, i = 0; i < delta.length; i++) { @@ -1082,13 +1084,14 @@ exports.decryptPassword = async function (ctx, password) { salt, initializationVector, authTag, - iterations + encryptedData ] = pointerArray; - const decryptionKey = await pbkdf2Promise(tenSecret, salt, parseInt(iterations.toString(), 10), keyByteLength, 'sha512'); + + const decryptionKey = await pbkdf2Promise(tenSecret, salt, parseInt(iterations, 16), keyByteLength, 'sha512'); const decipher = crypto.createDecipheriv('aes-256-gcm', decryptionKey, initializationVector); decipher.setAuthTag(authTag); - return Buffer.concat([decipher.update(password, 'binary'), decipher.final()]).toString(); + return Buffer.concat([decipher.update(encryptedData, 'binary'), decipher.final()]).toString(); }; exports.getDateTimeTicks = function(date) { return BigInt(date.getTime() * 10000) + 621355968000000000n; @@ -1242,4 +1245,4 @@ function deepMergeObjects(target, ...sources) { return deepMergeObjects(target, ...sources); } exports.isObject = isObject; -exports.deepMergeObjects = deepMergeObjects; \ No newline at end of file +exports.deepMergeObjects = deepMergeObjects; diff --git a/tests/unit/utils.tests.js b/tests/unit/utils.tests.js new file mode 100644 index 00000000..9300050d --- /dev/null +++ b/tests/unit/utils.tests.js @@ -0,0 +1,30 @@ +const { describe, test, expect } = require('@jest/globals'); +const config = require('../../Common/node_modules/config'); + +const operationContext = require('../../Common/sources/operationContext'); +const utils = require('../../Common/sources/utils'); + +const ctx = new operationContext.Context(); +const minimumIterationsByteLength = 4; + + +describe('AES encryption & decryption', function () { + test('Iterations range', async function () { + const configuration = config.get('aesEncrypt.config'); + const encrypted = await utils.encryptPassword(ctx, 'secretstring'); + const { iterationsByteLength = 5 } = configuration; + + const [iterationsHex] = encrypted.split(':'); + const iterations = parseInt(iterationsHex, 16); + + const iterationsLength = iterationsByteLength < minimumIterationsByteLength ? minimumIterationsByteLength : iterationsByteLength; + expect(iterations).toBeGreaterThanOrEqual(Math.pow(10, iterationsLength - 1)); + expect(iterations).toBeLessThanOrEqual(Math.pow(10, iterationsLength) - 1); + }); + + test('Correct workflow', async function () { + const encrypted = await utils.encryptPassword(ctx, 'secretstring'); + const decrypted = await utils.decryptPassword(ctx, encrypted); + expect(decrypted).toEqual('secretstring'); + }); +});