because I got bored of customising my CV for every job
1import {
2 createCipheriv,
3 createDecipheriv,
4 randomBytes,
5 scryptSync,
6} from "node:crypto";
7import { Injectable } from "@nestjs/common";
8import { ConfigService } from "@nestjs/config";
9
10@Injectable()
11export class TokenEncryptionService {
12 private readonly algorithm = "aes-256-gcm";
13 private readonly masterKey: string;
14 private readonly ivLength = 16;
15 private readonly saltLength = 16;
16
17 constructor(private readonly configService: ConfigService) {
18 this.masterKey = this.configService.getOrThrow<string>("ENCRYPTION_KEY");
19 }
20
21 encrypt(plaintext: string): string {
22 const iv = randomBytes(this.ivLength);
23 const salt = randomBytes(this.saltLength);
24 const key = scryptSync(this.masterKey, salt, 32) as Buffer;
25 const cipher = createCipheriv(this.algorithm, key, iv);
26
27 let encrypted = cipher.update(plaintext, "utf8", "hex");
28 encrypted += cipher.final("hex");
29
30 const tag = cipher.getAuthTag();
31
32 return `${salt.toString("hex")}:${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
33 }
34
35 decrypt(encryptedData: string): string {
36 const parts = encryptedData.split(":");
37 if (parts.length !== 4) {
38 throw new Error("Invalid encrypted data format");
39 }
40
41 const [saltHex, ivHex, tagHex, encrypted] = parts;
42 if (!(saltHex && ivHex && tagHex && encrypted)) {
43 throw new Error("Invalid encrypted data format");
44 }
45
46 const salt = Buffer.from(saltHex, "hex");
47 const iv = Buffer.from(ivHex, "hex");
48 const tag = Buffer.from(tagHex, "hex");
49 const key = scryptSync(this.masterKey, salt, 32) as Buffer;
50
51 const decipher = createDecipheriv(this.algorithm, key, iv);
52 decipher.setAuthTag(tag);
53
54 let decrypted = decipher.update(encrypted, "hex", "utf8");
55 decrypted += decipher.final("utf8");
56
57 return decrypted;
58 }
59}