237 Zeilen
6.0 KiB
JavaScript
237 Zeilen
6.0 KiB
JavaScript
/**
|
|
* 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
|
|
}; |