Spaces:
Running
Running
| import crypto from 'crypto'; | |
| import { config } from '../config'; | |
| const ALGORITHM = 'aes-256-gcm'; | |
| const IV_LENGTH = 16; | |
| const TAG_LENGTH = 16; | |
| function getKey(): Buffer { | |
| const key = config.encryptionKey; | |
| if (!key || key.length < 32) { | |
| throw new Error('ENCRYPTION_KEY must be at least 32 characters long'); | |
| } | |
| return Buffer.from(key.padEnd(32, 'x').slice(0, 32), 'utf8'); | |
| } | |
| export function encrypt(text: string): { encrypted: string; iv: string; tag: string } { | |
| const key = getKey(); | |
| const iv = crypto.randomBytes(IV_LENGTH); | |
| const cipher = crypto.createCipheriv(ALGORITHM, key, iv); | |
| let encrypted = cipher.update(text, 'utf8', 'hex'); | |
| encrypted += cipher.final('hex'); | |
| const tag = cipher.getAuthTag().toString('hex'); | |
| return { | |
| encrypted, | |
| iv: iv.toString('hex'), | |
| tag, | |
| }; | |
| } | |
| export function encryptCombined(text: string): string { | |
| const result = encrypt(text); | |
| return `${result.encrypted}:${result.iv}:${result.tag}`; | |
| } | |
| export function decryptCombined(stored: string): string { | |
| const parts = stored.split(':'); | |
| if (parts.length < 3) throw new Error('Invalid encrypted data format'); | |
| const tag = parts.pop()!; | |
| const iv = parts.pop()!; | |
| const encrypted = parts.join(':'); | |
| return decrypt(encrypted, iv, tag); | |
| } | |
| export function decrypt(encrypted: string, ivHex: string, tagHex: string): string { | |
| const key = getKey(); | |
| const iv = Buffer.from(ivHex, 'hex'); | |
| const tag = Buffer.from(tagHex, 'hex'); | |
| const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); | |
| decipher.setAuthTag(tag); | |
| let decrypted = decipher.update(encrypted, 'hex', 'utf8'); | |
| decrypted += decipher.final('utf8'); | |
| return decrypted; | |
| } | |
| export function hashToken(token: string): string { | |
| return crypto.createHash('sha256').update(token).digest('hex'); | |
| } | |