import crypto from 'node:crypto';
import { env } from '../config/env.js';
const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 12;
const AUTH_TAG_LENGTH = 16;
// Derive a proper 32-byte key from the encryption key
function getKey(): Buffer {
return crypto.scryptSync(env.ENCRYPTION_KEY, 'glasscloud-salt', 32);
}
/**
* Encrypt a string using AES-256-GCM
*/
export function encrypt(plaintext: string): string {
const iv = crypto.randomBytes(IV_LENGTH);
const key = getKey();
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
// Combine: iv + authTag + encrypted
const combined = Buffer.concat([iv, authTag, encrypted]);
return combined.toString('base64');
}
/**
* Decrypt a string encrypted with AES-256-GCM
*/
export function decrypt(ciphertext: string): string {
const combined = Buffer.from(ciphertext, 'base64');
const key = getKey();
const iv = combined.subarray(0, IV_LENGTH);
const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
const encrypted = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]);
return decrypted.toString('utf8');
}
/**
* Generate a cryptographically secure random token
*/
export function generateToken(bytes: number = 32): string {
return crypto.randomBytes(bytes).toString('base64url');
}
/**
* Hash a token for storage (one-way)
*/
export function hashToken(token: string): string {
return crypto.createHash('sha256').update(token).digest('hex');
}
/**
* Verify a token against its hash
*/
export function verifyToken(token: string, hash: string): boolean {
const tokenHash = hashToken(token);
return crypto.timingSafeEqual(Buffer.from(tokenHash), Buffer.from(hash));
}