| | import 'dotenv/config'; |
| | import crypto from 'node:crypto'; |
| | const { webcrypto } = crypto; |
| |
|
| | |
| | const key = Buffer.from(process.env.CREDS_KEY ?? '', 'hex'); |
| | const iv = Buffer.from(process.env.CREDS_IV ?? '', 'hex'); |
| | const algorithm = 'AES-CBC'; |
| |
|
| | |
| |
|
| | export async function encrypt(value: string) { |
| | const cryptoKey = await webcrypto.subtle.importKey('raw', key, { name: algorithm }, false, [ |
| | 'encrypt', |
| | ]); |
| | const encoder = new TextEncoder(); |
| | const data = encoder.encode(value); |
| | const encryptedBuffer = await webcrypto.subtle.encrypt( |
| | { name: algorithm, iv: iv }, |
| | cryptoKey, |
| | data, |
| | ); |
| | return Buffer.from(encryptedBuffer).toString('hex'); |
| | } |
| |
|
| | export async function decrypt(encryptedValue: string) { |
| | const cryptoKey = await webcrypto.subtle.importKey('raw', key, { name: algorithm }, false, [ |
| | 'decrypt', |
| | ]); |
| | const encryptedBuffer = Buffer.from(encryptedValue, 'hex'); |
| | const decryptedBuffer = await webcrypto.subtle.decrypt( |
| | { name: algorithm, iv: iv }, |
| | cryptoKey, |
| | encryptedBuffer, |
| | ); |
| | const decoder = new TextDecoder(); |
| | return decoder.decode(decryptedBuffer); |
| | } |
| |
|
| | |
| |
|
| | export async function encryptV2(value: string) { |
| | const gen_iv = webcrypto.getRandomValues(new Uint8Array(16)); |
| | const cryptoKey = await webcrypto.subtle.importKey('raw', key, { name: algorithm }, false, [ |
| | 'encrypt', |
| | ]); |
| | const encoder = new TextEncoder(); |
| | const data = encoder.encode(value); |
| | const encryptedBuffer = await webcrypto.subtle.encrypt( |
| | { name: algorithm, iv: gen_iv }, |
| | cryptoKey, |
| | data, |
| | ); |
| | return Buffer.from(gen_iv).toString('hex') + ':' + Buffer.from(encryptedBuffer).toString('hex'); |
| | } |
| |
|
| | export async function decryptV2(encryptedValue: string) { |
| | const parts = encryptedValue.split(':'); |
| | if (parts.length === 1) { |
| | return parts[0]; |
| | } |
| | const gen_iv = Buffer.from(parts.shift() ?? '', 'hex'); |
| | const encrypted = parts.join(':'); |
| | const cryptoKey = await webcrypto.subtle.importKey('raw', key, { name: algorithm }, false, [ |
| | 'decrypt', |
| | ]); |
| | const encryptedBuffer = Buffer.from(encrypted, 'hex'); |
| | const decryptedBuffer = await webcrypto.subtle.decrypt( |
| | { name: algorithm, iv: gen_iv }, |
| | cryptoKey, |
| | encryptedBuffer, |
| | ); |
| | const decoder = new TextDecoder(); |
| | return decoder.decode(decryptedBuffer); |
| | } |
| |
|
| | |
| | const algorithm_v3 = 'aes-256-ctr'; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function encryptV3(value: string) { |
| | if (key.length !== 32) { |
| | throw new Error(`Invalid key length: expected 32 bytes, got ${key.length} bytes`); |
| | } |
| | const iv_v3 = crypto.randomBytes(16); |
| | const cipher = crypto.createCipheriv(algorithm_v3, key, iv_v3); |
| | const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]); |
| | return `v3:${iv_v3.toString('hex')}:${encrypted.toString('hex')}`; |
| | } |
| |
|
| | export function decryptV3(encryptedValue: string) { |
| | const parts = encryptedValue.split(':'); |
| | if (parts[0] !== 'v3') { |
| | throw new Error('Not a v3 encrypted value'); |
| | } |
| | const iv_v3 = Buffer.from(parts[1], 'hex'); |
| | const encryptedText = Buffer.from(parts.slice(2).join(':'), 'hex'); |
| | const decipher = crypto.createDecipheriv(algorithm_v3, key, iv_v3); |
| | const decrypted = Buffer.concat([decipher.update(encryptedText), decipher.final()]); |
| | return decrypted.toString('utf8'); |
| | } |
| |
|
| | export async function getRandomValues(length: number) { |
| | if (!Number.isInteger(length) || length <= 0) { |
| | throw new Error('Length must be a positive integer'); |
| | } |
| | const randomValues = new Uint8Array(length); |
| | webcrypto.getRandomValues(randomValues); |
| | return Buffer.from(randomValues).toString('hex'); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export async function hashBackupCode(input: string) { |
| | const encoder = new TextEncoder(); |
| | const data = encoder.encode(input); |
| | const hashBuffer = await webcrypto.subtle.digest('SHA-256', data); |
| | const hashArray = Array.from(new Uint8Array(hashBuffer)); |
| | return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); |
| | } |
| |
|