Multi-Hoster-Upload/lib/backup-crypto.js
Administrator 60498fecc4 fix: multiple backup import issues found in code review
- Single atomic write instead of two-phase (prevents split state on crash)
- Timestamped pre-import backup (multiple imports don't overwrite safety net)
- Fix UI refresh: correct function names + refresh globalSettings/alwaysOnTop
- Zero sensitive buffers (key, plaintext, decrypted) after use

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:28:25 +01:00

77 lines
2.1 KiB
JavaScript

const crypto = require('crypto');
const MAGIC = Buffer.from('MHU1');
const SALT_LEN = 16;
const IV_LEN = 12;
const TAG_LEN = 16;
const KEY_LEN = 32;
const ITERATIONS = 100_000;
const DIGEST = 'sha512';
const ALGO = 'aes-256-gcm';
function deriveKey(password, salt) {
return crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_LEN, DIGEST);
}
/**
* Encrypt a config object with a password.
* Returns a Buffer: MHU1 | salt(16) | iv(12) | tag(16) | ciphertext
*/
function encrypt(config, password) {
const plaintext = Buffer.from(JSON.stringify(config), 'utf-8');
const salt = crypto.randomBytes(SALT_LEN);
const iv = crypto.randomBytes(IV_LEN);
const key = deriveKey(password, salt);
const cipher = crypto.createCipheriv(ALGO, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
plaintext.fill(0);
key.fill(0);
return Buffer.concat([MAGIC, salt, iv, tag, encrypted]);
}
/**
* Decrypt a .mhu buffer with a password.
* Returns the config object or throws on invalid password/data.
*/
function decrypt(buffer, password) {
if (buffer.length < MAGIC.length + SALT_LEN + IV_LEN + TAG_LEN + 1) {
throw new Error('Ungültiges Backup-Format');
}
const magic = buffer.subarray(0, 4);
if (!magic.equals(MAGIC)) {
throw new Error('Keine gültige .mhu Backup-Datei');
}
let offset = MAGIC.length;
const salt = buffer.subarray(offset, offset += SALT_LEN);
const iv = buffer.subarray(offset, offset += IV_LEN);
const tag = buffer.subarray(offset, offset += TAG_LEN);
const ciphertext = buffer.subarray(offset);
const key = deriveKey(password, salt);
const decipher = crypto.createDecipheriv(ALGO, key, iv);
decipher.setAuthTag(tag);
let decrypted;
try {
decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
} catch {
throw new Error('Falsches Passwort oder beschädigte Datei');
}
try {
return JSON.parse(decrypted.toString('utf-8'));
} catch {
throw new Error('Entschlüsselte Daten sind kein gültiges JSON');
} finally {
decrypted.fill(0);
key.fill(0);
}
}
module.exports = { encrypt, decrypt };