Files
app/src/utils/encryption.ts
2026-03-24 19:59:14 +01:00

59 lines
1.9 KiB
TypeScript

import crypto from "crypto";
import { config } from "../config/env";
const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 12;
const TAG_LENGTH = 16;
export function encrypt(plaintext: string): string {
const key = Buffer.from(config.totp.encryptionKey, "hex");
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, "utf8"),
cipher.final(),
]);
const tag = cipher.getAuthTag();
// Use PHP-compatible format: base64(nonce + ciphertext + tag)
return Buffer.concat([iv, encrypted, tag]).toString("base64");
}
export function decrypt(ciphertext: string): string {
const key = Buffer.from(config.totp.encryptionKey, "hex");
// Detect format: PHP uses base64(nonce+ciphertext+tag), TS uses hex:hex:hex
const parts = ciphertext.split(":");
if (parts.length === 3) {
// TS format: iv:encrypted:tag (hex)
const iv = Buffer.from(parts[0], "hex");
const encrypted = parts[1];
const tag = Buffer.from(parts[2], "hex");
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
// PHP format: base64(nonce + ciphertext + tag)
const raw = Buffer.from(ciphertext, "base64");
if (raw.length < IV_LENGTH + TAG_LENGTH + 1) {
throw new Error("Invalid ciphertext format");
}
const iv = raw.subarray(0, IV_LENGTH);
const tag = raw.subarray(raw.length - TAG_LENGTH);
const encrypted = raw.subarray(IV_LENGTH, raw.length - TAG_LENGTH);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted);
const final = decipher.final();
return Buffer.concat([decrypted, final]).toString("utf8");
}