/** * TASKMATE - Encryption Utilities * ================================ * Verschlüsselung für Backups und sensitive Daten */ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); const logger = require('./logger'); const ALGORITHM = 'aes-256-cbc'; const KEY_LENGTH = 32; // 256 bits const IV_LENGTH = 16; // 128 bits const SALT_LENGTH = 32; const TAG_LENGTH = 16; /** * Encryption Key aus Umgebung oder generiert */ function getEncryptionKey() { let key = process.env.ENCRYPTION_KEY; if (!key) { // Generiere neuen Key falls nicht vorhanden key = crypto.randomBytes(KEY_LENGTH).toString('hex'); logger.warn('Encryption Key wurde automatisch generiert. Speichere ihn in der .env: ENCRYPTION_KEY=' + key); return Buffer.from(key, 'hex'); } // Validiere Key-Length if (key.length !== KEY_LENGTH * 2) { // Hex-String ist doppelt so lang throw new Error(`Encryption Key muss ${KEY_LENGTH * 2} Hex-Zeichen haben`); } return Buffer.from(key, 'hex'); } /** * Key aus Passwort ableiten (PBKDF2) */ function deriveKeyFromPassword(password, salt) { return crypto.pbkdf2Sync(password, salt, 100000, KEY_LENGTH, 'sha256'); } /** * Datei verschlüsseln */ function encryptFile(inputPath, outputPath, password = null) { try { const data = fs.readFileSync(inputPath); // Salt und IV generieren const salt = crypto.randomBytes(SALT_LENGTH); const iv = crypto.randomBytes(IV_LENGTH); // Key ableiten const key = password ? deriveKeyFromPassword(password, salt) : getEncryptionKey(); // Verschlüsselung const cipher = crypto.createCipheriv(ALGORITHM, key, iv); const encrypted = Buffer.concat([cipher.update(data), cipher.final()]); // Header + Salt + IV + verschlüsselte Daten const header = Buffer.from('TMENC001', 'ascii'); // TaskMate Encryption v1 const result = Buffer.concat([ header, salt, iv, encrypted ]); fs.writeFileSync(outputPath, result); logger.info(`Datei verschlüsselt: ${path.basename(inputPath)} -> ${path.basename(outputPath)}`); return true; } catch (error) { logger.error(`Verschlüsselung fehlgeschlagen: ${error.message}`); return false; } } /** * Datei entschlüsseln */ function decryptFile(inputPath, outputPath, password = null) { try { const encryptedData = fs.readFileSync(inputPath); // Header prüfen const header = encryptedData.subarray(0, 8); if (header.toString('ascii') !== 'TMENC001') { throw new Error('Ungültiges verschlüsseltes Datei-Format'); } // Komponenten extrahieren let offset = 8; const salt = encryptedData.subarray(offset, offset + SALT_LENGTH); offset += SALT_LENGTH; const iv = encryptedData.subarray(offset, offset + IV_LENGTH); offset += IV_LENGTH; const encrypted = encryptedData.subarray(offset); // Key ableiten const key = password ? deriveKeyFromPassword(password, salt) : getEncryptionKey(); // Entschlüsselung const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]); fs.writeFileSync(outputPath, decrypted); logger.info(`Datei entschlüsselt: ${path.basename(inputPath)} -> ${path.basename(outputPath)}`); return true; } catch (error) { logger.error(`Entschlüsselung fehlgeschlagen: ${error.message}`); return false; } } /** * String verschlüsseln (für Passwörter etc.) */ function encryptString(plaintext, password = null) { try { const salt = crypto.randomBytes(SALT_LENGTH); const iv = crypto.randomBytes(IV_LENGTH); const key = password ? deriveKeyFromPassword(password, salt) : getEncryptionKey(); const cipher = crypto.createCipheriv(ALGORITHM, key, iv); const encrypted = Buffer.concat([ cipher.update(Buffer.from(plaintext, 'utf8')), cipher.final() ]); // Base64 kodiert zurückgeben const result = Buffer.concat([salt, iv, encrypted]); return result.toString('base64'); } catch (error) { logger.error(`String-Verschlüsselung fehlgeschlagen: ${error.message}`); return null; } } /** * String entschlüsseln */ function decryptString(encryptedString, password = null) { try { const data = Buffer.from(encryptedString, 'base64'); let offset = 0; const salt = data.subarray(offset, offset + SALT_LENGTH); offset += SALT_LENGTH; const iv = data.subarray(offset, offset + IV_LENGTH); offset += IV_LENGTH; const encrypted = data.subarray(offset); const key = password ? deriveKeyFromPassword(password, salt) : getEncryptionKey(); const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]); return decrypted.toString('utf8'); } catch (error) { logger.error(`String-Entschlüsselung fehlgeschlagen: ${error.message}`); return null; } } /** * Sicheres Löschen einer Datei (Überschreiben) */ function secureDelete(filePath) { try { if (!fs.existsSync(filePath)) { return true; } const stats = fs.statSync(filePath); const fileSize = stats.size; // Datei mehrfach mit Zufallsdaten überschreiben const fd = fs.openSync(filePath, 'r+'); for (let pass = 0; pass < 3; pass++) { const randomData = crypto.randomBytes(fileSize); fs.writeSync(fd, randomData, 0, fileSize, 0); fs.fsyncSync(fd); } fs.closeSync(fd); fs.unlinkSync(filePath); logger.info(`Datei sicher gelöscht: ${path.basename(filePath)}`); return true; } catch (error) { logger.error(`Sicheres Löschen fehlgeschlagen: ${error.message}`); return false; } } module.exports = { encryptFile, decryptFile, encryptString, decryptString, secureDelete, getEncryptionKey };