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 | 2x 2x 2x 2x 2x 10x 10x 10x 10x 1x 1x 1x 1x 1x 8x 8x 2x 7x 7x 5x 5x 5x 5x 5x 5x 5x 5x 2x 5x 2x 2x 3x 5x 3x 5x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x | /**
* 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');
}
|