learning_ai_common_plat/packages/field-encrypt/src/key-provider-memory.ts
saravanakumardb1 bb3f5385fc feat(field-encrypt): create @bytelyst/field-encrypt package with AES-256-GCM envelope encryption
- 10 source files: types, aes-gcm, 3 key providers (memory/env/akv), envelope, key-cache, dek-store, guards, migration, factory
- 42 Vitest tests: AES-GCM roundtrips, tamper detection, unicode, 100KB payloads, key providers, DEK cache TTL/LRU, envelope lifecycle, migration (dry-run + idempotent), config validation
- AKV MEK creation script (scripts/create-encryption-keys.sh) for 10 product MEKs
- .env.example updated with FIELD_ENCRYPT_* vars
2026-03-21 09:18:10 -07:00

49 lines
1.7 KiB
TypeScript

/**
* @bytelyst/field-encrypt — In-memory key provider
*
* For unit tests — no external dependencies.
* Generates a random MEK on instantiation. Wrapping is just XOR for simplicity in tests,
* but uses AES-256-GCM to match production semantics.
*/
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
import type { KeyProvider } from './types.js';
const ALGORITHM = 'aes-256-gcm';
const IV_BYTES = 12;
export class MemoryKeyProvider implements KeyProvider {
private readonly mek: Buffer;
private readonly version: string;
constructor(mek?: Buffer, version?: string) {
this.mek = mek ?? randomBytes(32);
this.version = version ?? 'memory-v1';
}
async wrapKey(dek: Buffer): Promise<{ wrappedKey: string; mekVersion: string }> {
const iv = randomBytes(IV_BYTES);
const cipher = createCipheriv(ALGORITHM, this.mek, iv);
let encrypted = cipher.update(dek);
encrypted = Buffer.concat([encrypted, cipher.final()]);
const tag = cipher.getAuthTag();
// Format: iv (12) + tag (16) + ciphertext
const wrapped = Buffer.concat([iv, tag, encrypted]);
return { wrappedKey: wrapped.toString('hex'), mekVersion: this.version };
}
async unwrapKey(wrappedKeyHex: string, _mekVersion: string): Promise<Buffer> {
const wrapped = Buffer.from(wrappedKeyHex, 'hex');
const iv = wrapped.subarray(0, IV_BYTES);
const tag = wrapped.subarray(IV_BYTES, IV_BYTES + 16);
const ciphertext = wrapped.subarray(IV_BYTES + 16);
const decipher = createDecipheriv(ALGORITHM, this.mek, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(ciphertext);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted;
}
}