48 lines
1.7 KiB
JavaScript
48 lines
1.7 KiB
JavaScript
|
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
||
|
|
|
||
|
|
import { config } from '../config.js';
|
||
|
|
|
||
|
|
const ALGORITHM = 'aes-256-gcm';
|
||
|
|
const IV_LEN = 12;
|
||
|
|
const TAG_LEN = 16;
|
||
|
|
|
||
|
|
let keyCache = null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Ключ из ENCRYPTION_KEY. Принимаем как 64-символьный hex (готовые 32 байта),
|
||
|
|
* так и произвольную парольную фразу — её растягиваем scrypt до 32 байт.
|
||
|
|
*/
|
||
|
|
function getKey() {
|
||
|
|
if (keyCache) return keyCache;
|
||
|
|
const raw = config.encryptionKey();
|
||
|
|
if (/^[0-9a-f]{64}$/i.test(raw)) {
|
||
|
|
keyCache = Buffer.from(raw, 'hex');
|
||
|
|
} else {
|
||
|
|
keyCache = scryptSync(raw, 'telegrambusiness/v1', 32);
|
||
|
|
}
|
||
|
|
return keyCache;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Шифрует буфер. Формат на выходе: [iv(12)][tag(16)][ciphertext].
|
||
|
|
* Так весь артефакт самодостаточен — расшифровка не требует внешних метаданных.
|
||
|
|
*/
|
||
|
|
export function encrypt(plaintext) {
|
||
|
|
const iv = randomBytes(IV_LEN);
|
||
|
|
const cipher = createCipheriv(ALGORITHM, getKey(), iv);
|
||
|
|
const data = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
||
|
|
return Buffer.concat([iv, cipher.getAuthTag(), data]);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function decrypt(payload) {
|
||
|
|
const iv = payload.subarray(0, IV_LEN);
|
||
|
|
const tag = payload.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
||
|
|
const data = payload.subarray(IV_LEN + TAG_LEN);
|
||
|
|
const decipher = createDecipheriv(ALGORITHM, getKey(), iv);
|
||
|
|
decipher.setAuthTag(tag);
|
||
|
|
return Buffer.concat([decipher.update(data), decipher.final()]);
|
||
|
|
}
|
||
|
|
|
||
|
|
export const encryptString = (text) => encrypt(Buffer.from(text, 'utf8')).toString('base64');
|
||
|
|
export const decryptString = (b64) => decrypt(Buffer.from(b64, 'base64')).toString('utf8');
|