chat-dev / server /cryptoUtils.js
incognitolm
Per-IP Guest Rate Limiter
8dde1b0
raw
history blame
1.94 kB
import crypto from 'crypto';
import fs from 'fs/promises';
import path from 'path';
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 16; // 128 bits for GCM
const AUTH_TAG_LENGTH = 16; // 128 bits
// Derive key from environment variable
function getKey() {
const keyEnv = process.env.DATA_ENCRYPTION_KEY;
if (!keyEnv) throw new Error('DATA_ENCRYPTION_KEY environment variable not set');
// Use SHA-256 to derive a 32-byte key from the env var
return crypto.createHash('sha256').update(keyEnv).digest();
}
export function encryptJson(data) {
const key = getKey();
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipher(ALGORITHM, key);
cipher.setAAD(Buffer.from('')); // Optional AAD
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
iv: iv.toString('hex'),
encrypted,
authTag: authTag.toString('hex'),
};
}
export function decryptJson(encryptedData) {
const key = getKey();
const { iv, encrypted, authTag } = encryptedData;
const decipher = crypto.createDecipher(ALGORITHM, key);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
decipher.setAAD(Buffer.from('')); // Match AAD
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
export async function saveEncryptedJson(filePath, data) {
const encrypted = encryptJson(data);
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, JSON.stringify(encrypted));
}
export async function loadEncryptedJson(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
const encrypted = JSON.parse(content);
return decryptJson(encrypted);
} catch (err) {
if (err.code === 'ENOENT') return null; // File not found
throw err;
}
}