Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 2m17s
Conventional commits / commits (pull_request) Successful in 31s
Backend security / security (pull_request) Successful in 8s
Backend verify / verify (pull_request) Failing after 1m56s
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
|
|
const VERSION_SIZE_BYTES = 1;
|
|
|
|
const KEY_SIZE_BYTES = 32;
|
|
|
|
const VERSION = 1;
|
|
const NONCE_SIZE = 12;
|
|
const TAG_SIZE = 16;
|
|
|
|
export class TbankCrypto {
|
|
constructor(private readonly key: Buffer) {}
|
|
|
|
static fromBase64(key: string): TbankCrypto {
|
|
const decoded = Buffer.from(key, 'base64');
|
|
if (decoded.length !== KEY_SIZE_BYTES) {
|
|
throw new Error('Invalid token encryption key');
|
|
}
|
|
return new TbankCrypto(decoded);
|
|
}
|
|
|
|
encrypt(value: string): string {
|
|
const nonce = randomBytes(NONCE_SIZE);
|
|
const cipher = createCipheriv('aes-256-gcm', this.key, nonce);
|
|
const ciphertext = Buffer.concat([
|
|
cipher.update(value, 'utf8'),
|
|
cipher.final(),
|
|
]);
|
|
return Buffer.concat([
|
|
Buffer.from([VERSION]),
|
|
nonce,
|
|
ciphertext,
|
|
cipher.getAuthTag(),
|
|
]).toString('base64');
|
|
}
|
|
|
|
decrypt(payload: string): string {
|
|
const bytes = Buffer.from(payload, 'base64');
|
|
if (bytes[0] !== VERSION || bytes.length < VERSION_SIZE_BYTES + NONCE_SIZE + TAG_SIZE) {
|
|
throw new Error('Invalid encrypted token');
|
|
}
|
|
const nonce = bytes.subarray(VERSION_SIZE_BYTES, VERSION_SIZE_BYTES + NONCE_SIZE);
|
|
const tag = bytes.subarray(-TAG_SIZE);
|
|
const decipher = createDecipheriv('aes-256-gcm', this.key, nonce);
|
|
decipher.setAuthTag(tag);
|
|
return Buffer.concat([
|
|
decipher.update(bytes.subarray(VERSION_SIZE_BYTES + NONCE_SIZE, -TAG_SIZE)),
|
|
decipher.final(),
|
|
]).toString('utf8');
|
|
}
|
|
}
|