/** * Pruebas del cifrado AES-256-GCM puro (crypto-core). * Cubre: round-trip, detección de manipulación (GCM), formato inválido, * unicidad del IV y validación de clave/longitud. */ import { describe, it, expect } from 'vitest'; import { randomBytes } from 'node:crypto'; import { encryptWithKey, decryptWithKey, isEncrypted, decodeKey, KEY_LENGTH, VERSION_PREFIX } from './crypto-core'; const KEY = randomBytes(KEY_LENGTH); describe('crypto-core AES-256-GCM', () => { it('round-trip: descifrar devuelve el texto original', () => { const plaintext = 'Soluciones01!'; const envelope = encryptWithKey(plaintext, KEY); expect(decryptWithKey(envelope, KEY)).toBe(plaintext); }); it('produce sobres con prefijo de versión gcm:', () => { const envelope = encryptWithKey('secreto', KEY); expect(envelope.startsWith(`${VERSION_PREFIX}:`)).toBe(true); expect(isEncrypted(envelope)).toBe(true); expect(isEncrypted('texto-plano')).toBe(false); expect(isEncrypted(null)).toBe(false); }); it('usa IV aleatorio: dos cifrados del mismo texto difieren', () => { const a = encryptWithKey('mismo', KEY); const b = encryptWithKey('mismo', KEY); expect(a).not.toBe(b); // pero ambos descifran al mismo valor expect(decryptWithKey(a, KEY)).toBe(decryptWithKey(b, KEY)); }); it('detecta manipulación del ciphertext (auth tag GCM)', () => { const envelope = encryptWithKey('integridad', KEY); const parts = envelope.split(':'); // Alterar un byte del ciphertext const tampered = Buffer.from(parts[3], 'base64'); tampered[0] = tampered[0] ^ 0xff; parts[3] = tampered.toString('base64'); expect(() => decryptWithKey(parts.join(':'), KEY)).toThrow(); }); it('falla al descifrar con clave incorrecta', () => { const envelope = encryptWithKey('secreto', KEY); const otherKey = randomBytes(KEY_LENGTH); expect(() => decryptWithKey(envelope, otherKey)).toThrow(); }); it('rechaza formato de sobre inválido', () => { expect(() => decryptWithKey('no-es-un-sobre', KEY)).toThrow(/Formato/); expect(() => decryptWithKey('aes:1:2:3', KEY)).toThrow(/Formato/); }); it('rechaza claves con longitud incorrecta', () => { expect(() => encryptWithKey('x', randomBytes(16))).toThrow(/32 bytes/); }); describe('decodeKey', () => { it('decodifica clave base64 de 32 bytes', () => { const b64 = randomBytes(KEY_LENGTH).toString('base64'); expect(decodeKey(b64).length).toBe(KEY_LENGTH); }); it('decodifica clave hex de 32 bytes', () => { const hex = randomBytes(KEY_LENGTH).toString('hex'); expect(decodeKey(hex).length).toBe(KEY_LENGTH); }); it('lanza si la clave está ausente', () => { expect(() => decodeKey(undefined)).toThrow(/ENCRYPTION_KEY/); expect(() => decodeKey(' ')).toThrow(/ENCRYPTION_KEY/); }); it('lanza si la clave no decodifica a 32 bytes', () => { expect(() => decodeKey('demasiado-corta')).toThrow(/32 bytes/); }); }); });