Replace createDecipher with createDecipheriv and add a compatibility mode for newer nodejs versions.

This commit is contained in:
Maik Marschner 2025-03-26 15:03:36 +01:00
parent ac1b554144
commit b2ae16b933
5 changed files with 76 additions and 39 deletions

View File

@ -10,6 +10,7 @@ module.exports = ({
userId, userId,
hostId, hostId,
passphrase, passphrase,
iv,
keyStoragePath, keyStoragePath,
} = loadConfig()) => new Client({ } = loadConfig()) => new Client({
url, url,
@ -17,5 +18,6 @@ module.exports = ({
userId, userId,
hostId, hostId,
passphrase, passphrase,
iv,
keyStorage: fsKeysStorage(keyStoragePath), keyStorage: fsKeysStorage(keyStoragePath),
}); });

View File

@ -43,7 +43,8 @@ const stringifyKeys = (keys) => {
* @property {string} partnerId - PARTNERID provided by the bank * @property {string} partnerId - PARTNERID provided by the bank
* @property {string} hostId - HOSTID provided by the bank * @property {string} hostId - HOSTID provided by the bank
* @property {string} userId - USERID 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 {KeyStorage} keyStorage - keyStorage implementation
* @property {object} [tracesStorage] - traces (logs) storage implementation * @property {object} [tracesStorage] - traces (logs) storage implementation
* @property {string} bankName - Full name of the bank to be used in the bank INI letters. * @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} 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. * @property {string} storageLocation - Location where to store the files that are downloaded. This can be a network share for example.
*/ */
module.exports = class Client { module.exports = class Client {
/** /**
*Creates an instance of Client. *Creates an instance of Client.
@ -63,6 +63,7 @@ module.exports = class Client {
userId, userId,
hostId, hostId,
passphrase, passphrase,
iv,
keyStorage, keyStorage,
tracesStorage, tracesStorage,
bankName, bankName,
@ -88,7 +89,7 @@ module.exports = class Client {
this.userId = userId; this.userId = userId;
this.hostId = hostId; this.hostId = hostId;
this.keyStorage = keyStorage; this.keyStorage = keyStorage;
this.keyEncryptor = defaultKeyEncryptor({ passphrase }); this.keyEncryptor = defaultKeyEncryptor({ passphrase, iv });
this.tracesStorage = tracesStorage || null; this.tracesStorage = tracesStorage || null;
this.bankName = bankName || 'Dummy Bank Full Name'; this.bankName = bankName || 'Dummy Bank Full Name';
this.bankShortName = bankShortName || 'BANKSHORTCODE'; this.bankShortName = bankShortName || 'BANKSHORTCODE';
@ -249,7 +250,6 @@ module.exports = class Client {
async keys() { async keys() {
try { try {
const keysString = await this._readKeys(); const keysString = await this._readKeys();
return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString))); return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString)));
} catch (err) { } catch (err) {
return null; return null;

View File

@ -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 };

View File

@ -1,24 +1,8 @@
'use strict'; 'use strict';
const crypto = require('crypto'); const { encrypt, decrypt } = require('../crypto/encryptDecrypt');
const Keys = require('./Keys'); 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') => { module.exports = (keysStorage, passphrase, algorithm = 'aes-256-cbc') => {
const storage = keysStorage; const storage = keysStorage;
const pass = passphrase; const pass = passphrase;

View File

@ -1,24 +1,9 @@
'use strict'; '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, iv, algorithm = 'aes-256-cbc' }) => ({
}; encrypt: data => encrypt(data, algorithm, passphrase, iv),
module.exports = ({
passphrase,
algorithm = 'aes-256-cbc',
}) => ({
encrypt: data => encrypt(data, algorithm, passphrase),
decrypt: data => decrypt(data, algorithm, passphrase), decrypt: data => decrypt(data, algorithm, passphrase),
}); });