Source: utils/crypto.js

import crypto from 'crypto'
import _ from 'lodash'
// AES-128 configuration
const algorithm = 'aes-128-cbc';

/**
 * Derives a key and IV from a passphrase.
 * @param {string} passphrase - The passphrase to derive key and IV from.
 * @returns {{key: Buffer, iv: Buffer, salt: string}} - The derived key, IV, and salt in base64 format.
 */
function deriveKeyAndIv(passphrase) {
    const salt = crypto.randomBytes(16); // Generate a random salt
    const keyIv = crypto.pbkdf2Sync(passphrase, salt, 100000, 32, 'sha256');
    return {
        key: keyIv.slice(0, 16),    // AES-128 requires a 16-byte key
        iv: keyIv.slice(16, 32),    // IV is also 16 bytes long
        salt: salt.toString('base64')
    };
}

/**
 * Encrypts a Base64-encoded string with AES-128 using a passphrase.
 * @param {string} base64String - The Base64-encoded string to encrypt.
 * @param {string} passphrase - The passphrase used to derive the encryption key and IV.
 * @returns {string} - The encrypted string in Base64 format.
 */
export function encrypt(string, passphrase= process.env.ibanPassphrase) {
    const { key, iv, salt } = deriveKeyAndIv(passphrase);
    const cipher = crypto.createCipheriv(algorithm, key, iv);
    let encrypted = cipher.update(string, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return `${salt}:${encrypted}`;
}


/**
 * Decrypts an AES-128 encrypted Base64 string using a passphrase.
 * @param {string} encryptedData - The encrypted string (Salt and encrypted text in Base64, separated by colons).
 *                                 Supports both old format (salt:iv:encrypted) and new format (salt:encrypted).
 * @param {string} passphrase - The passphrase used to derive the decryption key and IV.
 * @returns {string} - The decrypted Base64 string.
 */
export function decrypt(encryptedData, passphrase=process.env.ibanPassphrase) {
    try
    {
        const parts = encryptedData.split(':');
        const salt = Buffer.from(parts[0], 'base64');
        const keyIv = crypto.pbkdf2Sync(passphrase, salt, 100000, 32, 'sha256');
        const key = keyIv.slice(0, 16);
        
        let iv, encryptedBase64;
        
        // Handle both old format (salt:iv:encrypted) and new format (salt:encrypted)
        if (parts.length === 3) {
            // Old format: use stored IV (for backward compatibility)
            iv = Buffer.from(parts[1], 'base64');
            encryptedBase64 = parts[2];
        } else {
            // New format: derive IV from salt
            iv = keyIv.slice(16, 32);
            encryptedBase64 = parts[1];
        }
        
        const decipher = crypto.createDecipheriv(algorithm, key, iv);
        let decrypted = decipher.update(encryptedBase64, 'base64', 'utf8');
        decrypted += decipher.final('utf8');
        return decrypted;
    }
    catch(e)
    {
        console.log('decryption failed',e,encryptedData)
    }
 
}

export const encryptIbans= (config,data)=>
{

    if(data.accounts && data.accounts.length>0)
    {
        data.accounts.forEach(account=>
        {
            if( account?.IBAN)         
            {
                // no encryption
               
                
                    if(!process.env.ibanPassphrase || process.env.ibanPassphrase.length<12)
                    {
                        console.warn('IBAN passphrase is not set or too short. Skipping encryption.');
                        account.IBANshow=(account.IBAN.slice(0,account.IBAN.length-6)+'XXXXXX').replace(/(.{4})/g, `$1 `).trim()
                        return account;
                    }
                    const encrypted=encrypt(account.IBAN)
                    delete account.IBANdecrypt
                    account.IBANshow=(account.IBAN.slice(0,account.IBAN.length-6)+'XXXXXX').replace(/(.{4})/g, `$1 `).trim()
                    delete account.IBAN
                    account. IBANencrypted=encrypted
                
                
            }
        })
    }
}
    
export const decryptAccount=(account)=>
{
    if(account.IBANencrypted)
    {
        try{
            const decrypted=decrypt(account.IBANencrypted)
            if(decrypted.match(/[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}/)) 
                return {...account,IBAN:decrypted}
            else 
                return account
        }
        catch(e)
        {
            return account
        }
        
    }
    else
        return account

}

export const decryptIbans= (data)=>
{
    if(data.accounts && data.accounts.length>0)
    {
        data.accounts=data.accounts.map(account=>
        { 
            return decryptAccount(account)
        })
    }
}

export const accountsEqual=(newSet,oldSet)=>
{
    // this will compare two account Sets
    // the encryption is with random salt.
    // so we have tro treat IBANencrypted seperately
    // we have to exclude IBANencrypted to check for equality
    const newAccounts=newSet?.map(a => decryptAccount(a)) ?? []
    newAccounts.forEach(a=>{delete a.IBANencrypted})
     // we have to exclude IBANencrypted to check for equality
    const oldAccounts=oldSet?.map(a => decryptAccount(a)) ?? []
    oldAccounts.forEach(a=>{delete a.IBANencrypted})
    return _.isEqual(oldAccounts,newAccounts)
}