Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | 1x 1x 1x 1x 1x 1x 1x | /** * Token Encryption Utility * * AES-256-GCM encryption for sensitive tokens (OAuth access tokens, etc.) * Uses TOKEN_ENCRYPTION_KEY env var (64-char hex = 32 bytes). * Falls back to plaintext storage in development if key is not set. */ import crypto from 'crypto'; const ALGORITHM = 'aes-256-gcm'; const IV_LENGTH = 12; // 96-bit IV recommended for GCM const AUTH_TAG_LENGTH = 16; const ENCRYPTED_PREFIX = 'enc:'; function getEncryptionKey(): Buffer | null { const key = process.env.TOKEN_ENCRYPTION_KEY; if (!key) return null; if (key.length !== 64) { console.warn( '[token-encryption] TOKEN_ENCRYPTION_KEY must be 64 hex characters (32 bytes). Falling back to plaintext.' ); return null; } return Buffer.from(key, 'hex'); } /** * Encrypt a token string using AES-256-GCM. * Returns prefixed ciphertext: "enc:<iv>:<authTag>:<ciphertext>" (all base64). * Falls back to plaintext if TOKEN_ENCRYPTION_KEY is not set. */ export function encryptToken(plaintext: string): string { const key = getEncryptionKey(); if (!key) return plaintext; const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH, }); const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); const authTag = cipher.getAuthTag(); return `${ENCRYPTED_PREFIX}${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted.toString('base64')}`; } /** * Decrypt a token string encrypted with encryptToken(). * Handles both encrypted (prefixed) and legacy plaintext tokens. */ export function decryptToken(ciphertext: string): string { // Handle legacy plaintext tokens (not prefixed) if (!ciphertext.startsWith(ENCRYPTED_PREFIX)) { return ciphertext; } const key = getEncryptionKey(); if (!key) { console.warn( '[token-encryption] Cannot decrypt: TOKEN_ENCRYPTION_KEY not set. Returning raw value.' ); // Strip prefix and return raw (this shouldn't happen in production) return ciphertext.slice(ENCRYPTED_PREFIX.length); } const parts = ciphertext.slice(ENCRYPTED_PREFIX.length).split(':'); if (parts.length !== 3) { throw new Error('Invalid encrypted token format'); } const [ivB64, authTagB64, encryptedB64] = parts; const iv = Buffer.from(ivB64, 'base64'); const authTag = Buffer.from(authTagB64, 'base64'); const encrypted = Buffer.from(encryptedB64, 'base64'); const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH, }); decipher.setAuthTag(authTag); const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); return decrypted.toString('utf8'); } |