From b2ae16b933dfe4b460db57b0cd1feb3afbd75d28 Mon Sep 17 00:00:00 2001 From: Maik Marschner Date: Wed, 26 Mar 2025 15:03:36 +0100 Subject: [PATCH] Replace createDecipher with createDecipheriv and add a compatibility mode for newer nodejs versions. --- examples/getClient.js | 2 + lib/Client.js | 8 ++-- lib/crypto/encryptDecrypt.js | 66 ++++++++++++++++++++++++++ lib/keymanagers/KeysManager.js | 18 +------ lib/keymanagers/defaultKeyEncryptor.js | 21 ++------ 5 files changed, 76 insertions(+), 39 deletions(-) create mode 100644 lib/crypto/encryptDecrypt.js diff --git a/examples/getClient.js b/examples/getClient.js index ba701e6..410c9fc 100644 --- a/examples/getClient.js +++ b/examples/getClient.js @@ -10,6 +10,7 @@ module.exports = ({ userId, hostId, passphrase, + iv, keyStoragePath, } = loadConfig()) => new Client({ url, @@ -17,5 +18,6 @@ module.exports = ({ userId, hostId, passphrase, + iv, keyStorage: fsKeysStorage(keyStoragePath), }); diff --git a/lib/Client.js b/lib/Client.js index f4baebd..f345e38 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -43,7 +43,8 @@ const stringifyKeys = (keys) => { * @property {string} partnerId - PARTNERID provided by the bank * @property {string} hostId - HOSTID provided by the bank * @property {string} userId - USERID provided by the bank - * @property {string} passphrase - passphrase for keys encryption + * @property {string|Buffer} passphrase - passphrase or key for keys encryption + * @property {string|Buffer} iv - Initialization Vector for keys encryption * @property {KeyStorage} keyStorage - keyStorage implementation * @property {object} [tracesStorage] - traces (logs) storage implementation * @property {string} bankName - Full name of the bank to be used in the bank INI letters. @@ -51,7 +52,6 @@ const stringifyKeys = (keys) => { * @property {string} languageCode - Language code to be used in the bank INI letters ("de", "en" and "fr" are currently supported). * @property {string} storageLocation - Location where to store the files that are downloaded. This can be a network share for example. */ - module.exports = class Client { /** *Creates an instance of Client. @@ -63,6 +63,7 @@ module.exports = class Client { userId, hostId, passphrase, + iv, keyStorage, tracesStorage, bankName, @@ -88,7 +89,7 @@ module.exports = class Client { this.userId = userId; this.hostId = hostId; this.keyStorage = keyStorage; - this.keyEncryptor = defaultKeyEncryptor({ passphrase }); + this.keyEncryptor = defaultKeyEncryptor({ passphrase, iv }); this.tracesStorage = tracesStorage || null; this.bankName = bankName || 'Dummy Bank Full Name'; this.bankShortName = bankShortName || 'BANKSHORTCODE'; @@ -249,7 +250,6 @@ module.exports = class Client { async keys() { try { const keysString = await this._readKeys(); - return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString))); } catch (err) { return null; diff --git a/lib/crypto/encryptDecrypt.js b/lib/crypto/encryptDecrypt.js new file mode 100644 index 0000000..7fd9c91 --- /dev/null +++ b/lib/crypto/encryptDecrypt.js @@ -0,0 +1,66 @@ +'use strict'; + +const crypto = require('crypto'); + +const createKeyAndIv = (passphrase) => { + // this generates a 256-bit key and a 128-bit iv for aes-256-cbc + // just like nodejs's deprecated/removed crypto.createCipher would + const a = crypto.createHash('md5').update(passphrase).digest(); + const b = crypto + .createHash('md5') + .update(Buffer.concat([a, Buffer.from(passphrase)])) + .digest(); + const c = crypto + .createHash('md5') + .update(Buffer.concat([b, Buffer.from(passphrase)])) + .digest(); + const bytes = Buffer.concat([a, b, c]); + const key = bytes.subarray(0, 32); + const iv = bytes.subarray(32, 48); + return { key, iv }; +}; + +const encrypt = (data, algorithm, passphrase, iv) => { + let cipher; + if (iv) { + cipher = crypto.createCipheriv(algorithm, passphrase, iv); + } else { + console.warn( + '[Deprecation notice] No IV provided, falling back to legacy key derivation.\n' + + 'This will be removed in a future major release. You should encrypt your keys with a proper key and IV.', + ); + if (crypto.createCipher) { + // nodejs < 22 + cipher = crypto.createCipher(algorithm, passphrase); + } else { + const { key, iv: generatedIv } = createKeyAndIv(passphrase); + cipher = crypto.createCipheriv(algorithm, key, generatedIv); + } + } + const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex'); + return Buffer.from(encrypted).toString('base64'); +}; + +const decrypt = (data, algorithm, passphrase, iv) => { + data = Buffer.from(data, 'base64').toString(); + let decipher; + if (iv) { + decipher = crypto.createDecipheriv(algorithm, passphrase, iv); + } else { + console.warn( + '[Deprecation notice] No IV provided, falling back to legacy key derivation.\n' + + 'This will be removed in a future major release. You should re-encrypt your keys with a proper key and IV.', + ); + if (crypto.createDecipher) { + // nodejs < 22 + decipher = crypto.createDecipher(algorithm, passphrase); + } else { + const { key, iv: generatedIv } = createKeyAndIv(passphrase); + decipher = crypto.createDecipheriv(algorithm, key, generatedIv); + } + } + const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8'); + return decrypted; +}; + +module.exports = { encrypt, decrypt }; diff --git a/lib/keymanagers/KeysManager.js b/lib/keymanagers/KeysManager.js index e82c724..d9e5528 100644 --- a/lib/keymanagers/KeysManager.js +++ b/lib/keymanagers/KeysManager.js @@ -1,24 +1,8 @@ 'use strict'; -const crypto = require('crypto'); - +const { encrypt, decrypt } = require('../crypto/encryptDecrypt'); const Keys = require('./Keys'); -const encrypt = (data, algorithm, passphrase) => { - const cipher = crypto.createCipher(algorithm, passphrase); - const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex'); - - return Buffer.from(encrypted).toString('base64'); -}; -const decrypt = (data, algorithm, passphrase) => { - data = (Buffer.from(data, 'base64')).toString(); - - const decipher = crypto.createDecipher(algorithm, passphrase); - const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8'); - - return decrypted; -}; - module.exports = (keysStorage, passphrase, algorithm = 'aes-256-cbc') => { const storage = keysStorage; const pass = passphrase; diff --git a/lib/keymanagers/defaultKeyEncryptor.js b/lib/keymanagers/defaultKeyEncryptor.js index be387f7..f80d2d3 100644 --- a/lib/keymanagers/defaultKeyEncryptor.js +++ b/lib/keymanagers/defaultKeyEncryptor.js @@ -1,24 +1,9 @@ 'use strict'; -const crypto = require('crypto'); +const { encrypt, decrypt } = require('../crypto/encryptDecrypt'); -const encrypt = (data, algorithm, passphrase) => { - const cipher = crypto.createCipher(algorithm, passphrase); - const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex'); - return Buffer.from(encrypted).toString('base64'); -}; -const decrypt = (data, algorithm, passphrase) => { - data = (Buffer.from(data, 'base64')).toString(); - const decipher = crypto.createDecipher(algorithm, passphrase); - const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8'); - return decrypted; -}; - -module.exports = ({ - passphrase, - algorithm = 'aes-256-cbc', -}) => ({ - encrypt: data => encrypt(data, algorithm, passphrase), +module.exports = ({ passphrase, iv, algorithm = 'aes-256-cbc' }) => ({ + encrypt: data => encrypt(data, algorithm, passphrase, iv), decrypt: data => decrypt(data, algorithm, passphrase), });