Files
Momento/memento-note/lib/crypto.ts
Antigravity a623454347
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m32s
CI / Deploy production (on server) (push) Has been skipped
perf: memo GridCard, fuse save fns, fix slash tab active color
2026-06-14 14:06:05 +00:00

67 lines
2.0 KiB
TypeScript

import {
createCipheriv,
createDecipheriv,
createHash,
randomBytes,
scrypt,
} from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY_LEN = 32;
const IV_LEN = 16;
const SALT_LEN = 16;
const TAG_LEN = 16;
function getMasterPassphrase(): string {
const master = process.env.MASTER_ENCRYPTION_KEY;
if (!master || master.length < 32) {
console.warn('[crypto] WARNING: MASTER_ENCRYPTION_KEY is not set or is too short. Using development fallback key.');
return 'memento-development-encryption-key-32-chars';
}
return master;
}
async function deriveKey(salt: Buffer): Promise<Buffer> {
return new Promise((resolve, reject) => {
scrypt(getMasterPassphrase(), salt, KEY_LEN, (err, key) => {
if (err) reject(err);
else resolve(key);
});
});
}
export async function encryptApiKey(plaintext: string): Promise<string> {
const salt = randomBytes(SALT_LEN);
const iv = randomBytes(IV_LEN);
const key = await deriveKey(salt);
const cipher = createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
}
export async function decryptApiKey(payload: string): Promise<string> {
const buf = Buffer.from(payload, 'base64');
if (buf.length < SALT_LEN + IV_LEN + TAG_LEN + 1) {
throw new Error('Invalid encrypted key payload');
}
const salt = buf.subarray(0, SALT_LEN);
const iv = buf.subarray(SALT_LEN, SALT_LEN + IV_LEN);
const tag = buf.subarray(SALT_LEN + IV_LEN, SALT_LEN + IV_LEN + TAG_LEN);
const ciphertext = buf.subarray(SALT_LEN + IV_LEN + TAG_LEN);
const key = await deriveKey(salt);
const decipher = createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]).toString('utf8');
}
export function hashApiKey(plaintext: string): string {
return createHash('sha256').update(plaintext, 'utf8').digest('hex');
}