diff --git a/.env b/.env index 1ed8778..e55fda6 100644 --- a/.env +++ b/.env @@ -1,9 +1,14 @@ NODE_ENV= # TWILIO -TWILIO_ACCOUNT_SID= -TWILIO_AUTH_TOKEN= -TWILIO_PHONE_NUMBER= +TWILIO_ACCOUNT_SID=secret_do_not_commit_or_change_this_create_.env.local_instead +TWILIO_AUTH_TOKEN=secret_do_not_commit_or_change_this_create_.env.local_instead +TWILIO_PHONE_NUMBER=secret_do_not_commit_or_change_this_create_.env.local_instead + +# SECRETS +SECRETS_PASSWORD=secret_do_not_commit_or_change_this_create_.env.local_instead +SECRETS_SALT=secret_do_not_commit_or_change_this_create_.env.local_instead +SECRETS_IV_POSITION=secret_do_not_commit_or_change_this_create_.env.local_instead # PRISMA DATABASE_URL="postgres://hestia:test123@localhost:5432/hestia" diff --git a/scripts/validate-env.ts b/scripts/validate-env.ts index da28326..888b6fd 100644 --- a/scripts/validate-env.ts +++ b/scripts/validate-env.ts @@ -7,6 +7,9 @@ const ValidateEnvironment = () => { TWILIO_ACCOUNT_SID: z.string().min(1), TWILIO_AUTH_TOKEN: z.string().min(1), TWILIO_PHONE_NUMBER: z.string().regex(PhoneRegex), + SECRETS_PASSWORD: z.string().length(32), + SECRETS_SALT: z.string().min(16), + SECRETS_IV_POSITION: z.number().positive(), }) .safeParse(process.env); diff --git a/src/lib/server/crypto/encryption.test.ts b/src/lib/server/crypto/encryption.test.ts new file mode 100644 index 0000000..3d421ae --- /dev/null +++ b/src/lib/server/crypto/encryption.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it, vi } from 'vitest'; +import { decrypt, encrypt } from './encryption'; + +describe('Encryption', () => { + it('should encrypt and decrypt data', () => { + const data = 'aye its ya boi!'; + + vi.mock('$env/static/private', () => ({ + SECRETS_PASSWORD: 'aac7405eb3384e68c285fc252dbf68b2', + SECRETS_SALT: 'c4aeaf8bda72ea45e8c23269ca849013', + SECRETS_IV_POSITION: 9, + })); + + const encrypted = encrypt(data); + console.log(encrypted); + const decrypted = decrypt(encrypted); + + expect(decrypted).toEqual(data); + }); +}); diff --git a/src/lib/server/crypto/encryption.ts b/src/lib/server/crypto/encryption.ts new file mode 100644 index 0000000..3e077db --- /dev/null +++ b/src/lib/server/crypto/encryption.ts @@ -0,0 +1,39 @@ +import { SECRETS_PASSWORD, SECRETS_IV_POSITION, SECRETS_SALT } from '$env/static/private'; +import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto'; + +const algorithm = 'aes-256-gcm'; +const password = SECRETS_PASSWORD; +const salt = SECRETS_SALT; +const iv_position = Number(SECRETS_IV_POSITION); + +function construct(encrypted: string, tag: Buffer, iv: { value: Buffer; position: number }) { + return `${encrypted.slice(0, iv.position)}${iv.value.toString('hex')}${encrypted.slice(iv.position)}${tag.toString('hex')}`; +} + +function deconstruct(encrypted: string, position: number): [Buffer, string, Buffer] { + const iv = encrypted.slice(position, position + 16); + const text = `${encrypted.slice(0, position)}${encrypted.slice(position + 16, encrypted.length - 32)}`; + const authTag = encrypted.slice(encrypted.length - 32); + return [Buffer.from(iv, 'hex'), text, Buffer.from(authTag, 'hex')]; +} + +export function encrypt(value: string): string { + const key = scryptSync(password, salt, 32); + const iv = randomBytes(8); + + const cipher = createCipheriv(algorithm, key, iv); + const encrypted = cipher.update(value, 'utf-8', 'hex') + cipher.final('hex'); + const authTag = cipher.getAuthTag(); + + return construct(encrypted, authTag, { value: iv, position: iv_position }); +} + +export function decrypt(value: string): string { + const key = scryptSync(password, salt, 32); + const [iv, text, authTag] = deconstruct(value, iv_position); + + const decipher = createDecipheriv(algorithm, key, iv); + decipher.setAuthTag(authTag); + + return decipher.update(text, 'hex', 'utf-8') + decipher.final('utf-8'); +} diff --git a/src/lib/server/crypto/index.ts b/src/lib/server/crypto/index.ts new file mode 100644 index 0000000..73ebae8 --- /dev/null +++ b/src/lib/server/crypto/index.ts @@ -0,0 +1 @@ +export * from './encryption';