Files
PANEL_BASES_ANEXO24/src/lib/server/crypto-core.test.ts
hreyes b8be307892 feature/integracion-cloudrestore-targets (#9)
integraciones para cloud recovery

Reviewed-on: #9
Co-authored-by: hreyes <hreyes@aduanasoft.com.mx>
Co-committed-by: hreyes <hreyes@aduanasoft.com.mx>
2026-06-09 14:58:48 +00:00

88 lines
3.2 KiB
TypeScript

/**
* 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/);
});
});
});