add encryption/decryption + env vars

This commit is contained in:
Benjamin Palko 2025-01-08 16:14:24 -05:00
parent 2144ae053d
commit bc2be3302a
5 changed files with 71 additions and 3 deletions

11
.env
View file

@ -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"

View file

@ -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);

View file

@ -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);
});
});

View file

@ -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');
}

View file

@ -0,0 +1 @@
export * from './encryption';