import crypto from 'crypto' // Verschlüsselungskonfiguration // v1 (legacy): aes-256-cbc (ohne Authentizitätsschutz) const LEGACY_ALGORITHM = 'aes-256-cbc' const LEGACY_IV_LENGTH = 16 // v2 (default): aes-256-gcm (AEAD, state-of-the-art) const ALGORITHM = 'aes-256-gcm' const IV_LENGTH = 12 const AUTH_TAG_LENGTH = 16 const SALT_LENGTH = 32 const VERSION_PREFIX = 'v2:' /** * Generiert einen Schlüssel aus einem Passwort und Salt */ function deriveKey(password, salt) { return crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha512') } function encryptV2GCM(text, password) { // Salt generieren const salt = crypto.randomBytes(SALT_LENGTH) // Schlüssel ableiten const key = deriveKey(password, salt) // IV generieren (12 bytes ist Best Practice für GCM) const iv = crypto.randomBytes(IV_LENGTH) // Cipher erstellen const cipher = crypto.createCipheriv(ALGORITHM, key, iv) // Verschlüsseln const encrypted = Buffer.concat([ cipher.update(text, 'utf8'), cipher.final() ]) // Auth-Tag holen const tag = cipher.getAuthTag() // Salt + IV + Tag + Ciphertext kombinieren const combined = Buffer.concat([salt, iv, tag, encrypted]) return `${VERSION_PREFIX}${combined.toString('base64')}` } function decryptLegacyCBC(encryptedData, password) { // Base64 dekodieren const combined = Buffer.from(encryptedData, 'base64') // Komponenten extrahieren (v1: salt(32) + iv(16) + ciphertext) const salt = combined.subarray(0, SALT_LENGTH) const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + LEGACY_IV_LENGTH) const encrypted = combined.subarray(SALT_LENGTH + LEGACY_IV_LENGTH) // Schlüssel ableiten const key = deriveKey(password, salt) // Decipher erstellen const decipher = crypto.createDecipheriv(LEGACY_ALGORITHM, key, iv) // Entschlüsseln const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]) return decrypted.toString('utf8') } function decryptV2GCM(encryptedData, password) { const b64 = encryptedData.slice(VERSION_PREFIX.length) const combined = Buffer.from(b64, 'base64') // v2: salt(32) + iv(12) + tag(16) + ciphertext const salt = combined.subarray(0, SALT_LENGTH) const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH) const tagStart = SALT_LENGTH + IV_LENGTH const tag = combined.subarray(tagStart, tagStart + AUTH_TAG_LENGTH) const encrypted = combined.subarray(tagStart + AUTH_TAG_LENGTH) const key = deriveKey(password, salt) const decipher = crypto.createDecipheriv(ALGORITHM, key, iv) decipher.setAuthTag(tag) const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]) return decrypted.toString('utf8') } /** * Verschlüsselt einen Text */ export function encrypt(text, password) { try { return encryptV2GCM(text, password) } catch (error) { console.error('Verschlüsselungsfehler:', error) throw new Error('Fehler beim Verschlüsseln der Daten') } } /** * Entschlüsselt einen Text */ export function decrypt(encryptedData, password) { try { if (typeof encryptedData === 'string' && encryptedData.startsWith(VERSION_PREFIX)) { return decryptV2GCM(encryptedData, password) } // Fallback: legacy CBC ohne Prefix return decryptLegacyCBC(encryptedData, password) } catch (error) { console.error('Entschlüsselungsfehler:', error) throw new Error('Fehler beim Entschlüsseln der Daten') } } /** * Verschlüsselt ein Objekt (konvertiert zu JSON) */ export function encryptObject(obj, password) { const jsonString = JSON.stringify(obj) return encrypt(jsonString, password) } /** * Entschlüsselt ein Objekt (konvertiert von JSON) */ export function decryptObject(encryptedData, password) { const jsonString = decrypt(encryptedData, password) return JSON.parse(jsonString) } /** * Generiert einen sicheren Schlüssel für die Datenverschlüsselung */ export function generateEncryptionKey() { return crypto.randomBytes(32).toString('hex') }