#!/usr/bin/env node /** * 2FAuth Backup Decryptor * ----------------------- * A standalone script to decrypt 2FAuth backup files. * * Usage: node decrypt_backup.js */ const fs = require('fs'); const crypto = require('crypto'); const path = require('path'); // Crypto Configuration (Must match 2FAuth standards) const CRYPTO_CONFIG = { ALGO_NAME: 'aes-256-gcm', KDF_NAME: 'sha256', ITERATIONS: 100000, KEY_LEN: 32, SALT_LEN: 16, IV_LEN: 12, TAG_LEN: 16 }; function main() { const args = process.argv.slice(2); if (args.length < 2) { console.log('\x1b[33m%s\x1b[0m', 'Usage: node decrypt_backup.js '); process.exit(1); } const filePath = path.resolve(args[0]); const password = args[1]; if (!fs.existsSync(filePath)) { console.error('\x1b[31m%s\x1b[0m', `Error: File not found at ${filePath}`); process.exit(1); } let backupContent; try { const fileData = fs.readFileSync(filePath, 'utf8'); backupContent = JSON.parse(fileData); } catch (e) { console.error('\x1b[31m%s\x1b[0m', `Error: Failed to read or parse JSON file: ${e.message}`); process.exit(1); } if (!backupContent.encrypted || !backupContent.data) { console.error('\x1b[31m%s\x1b[0m', 'Error: This file does not appear to be an encrypted 2FAuth backup or it is already decrypted.'); process.exit(1); } try { const decryptedData = decrypt(backupContent.data, password); const prettyJson = JSON.stringify(decryptedData, null, 2); console.log('\x1b[32m%s\x1b[0m', '✅ Decryption Successful!'); console.log('\x1b[36m%s\x1b[0m', '--- Decrypted Content ---'); console.log(prettyJson); console.log('\x1b[36m%s\x1b[0m', '-------------------------'); // Optional: Save to file const outputFilename = `decrypted-${path.basename(filePath)}`; fs.writeFileSync(outputFilename, prettyJson); console.log('\x1b[32m%s\x1b[0m', `Result saved to: ${outputFilename}`); } catch (e) { console.error('\x1b[31m%s\x1b[0m', `❌ Decryption Failed: ${e.message}`); console.error('\x1b[33m%s\x1b[0m', 'Check if your password is correct.'); process.exit(1); } } /** * Decrypts the base64 data string using the provided password */ function decrypt(base64Data, password) { const combined = Buffer.from(base64Data, 'base64'); // Structure: [salt(16)] + [iv(12)] + [ciphertext(remaining - 16)] + [tag(16)] // Note: Node.js decipher.setAuthTag requires the tag to be separated. const salt = combined.subarray(0, CRYPTO_CONFIG.SALT_LEN); const iv = combined.subarray(CRYPTO_CONFIG.SALT_LEN, CRYPTO_CONFIG.SALT_LEN + CRYPTO_CONFIG.IV_LEN); const authTag = combined.subarray(combined.length - CRYPTO_CONFIG.TAG_LEN); const ciphertext = combined.subarray(CRYPTO_CONFIG.SALT_LEN + CRYPTO_CONFIG.IV_LEN, combined.length - CRYPTO_CONFIG.TAG_LEN); // Derive key const key = crypto.pbkdf2Sync( password, salt, CRYPTO_CONFIG.ITERATIONS, CRYPTO_CONFIG.KEY_LEN, CRYPTO_CONFIG.KDF_NAME ); // Decrypt const decipher = crypto.createDecipheriv(CRYPTO_CONFIG.ALGO_NAME, key, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(ciphertext, null, 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } main();