Una cadena de aspecto ilegible puede ser el resultado de un hash, de un cifrado o de una codificación. Aunque las tres operaciones transforman datos, responden a preguntas distintas: ¿quiero comparar un contenido sin conservarlo?, ¿quiero protegerlo y recuperarlo?, ¿o solo necesito representarlo en otro formato? Confundirlas puede dejar secretos expuestos o hacer imposible recuperar información que sí necesitabas.
En esta guía partiremos de esas preguntas y probaremos tres ejemplos locales con Node.js: SHA-256, AES-256-GCM y Base64. No necesitas conocimientos previos de criptografía; sí conviene distinguir entre los bytes de un dato y la forma en que los mostramos como texto.
Laboratorio: necesitas Node.js 22 o superior y una terminal. Cada bloque usa únicamente módulos integrados y datos ficticios; guárdalo en el archivo .mjs indicado y ejecútalo en tu equipo.
Primero: datos, bytes y representaciones
Un texto como Hola se convierte en bytes, por ejemplo mediante UTF-8. Una operación criptográfica trabaja sobre esos bytes; después podemos mostrar su resultado como hexadecimal o Base64. El formato visible del resultado no revela por sí solo qué operación se hizo. Un hash puede imprimirse en hexadecimal; un texto cifrado puede imprimirse en Base64.
| Técnica | Objetivo principal | ¿Usa una clave secreta? | ¿Se recupera el dato original? |
|---|---|---|---|
| Hash criptográfico, como SHA-256 | Obtener una huella de longitud fija para comparar datos | No | No hay una operación inversa. Si el dato es predecible, alguien puede probar candidatos y comparar hashes. |
| Cifrado autenticado, como AES-256-GCM | Proteger la confidencialidad y detectar modificaciones | Sí | Sí, con la clave y los datos auxiliares correctos. |
| Codificación, como Base64 | Representar bytes mediante caracteres de un alfabeto limitado | No | Sí, decodificando; no ofrece secreto ni autenticidad. |
Estas propiedades se desprenden, respectivamente, del estándar SHA-2 de NIST, de la especificación de GCM de NIST y de RFC 4648. Son propiedades técnicas; el uso concreto de un algoritmo no convierte automáticamente un sistema en seguro.
Hashing con SHA-256: una huella, no una caja cerrada
SHA-256 produce un resumen de 256 bits para una entrada de longitud variable. La misma secuencia de bytes produce el mismo resumen. Una entrada distinta normalmente produce otro, pero, como la salida tiene tamaño fijo, las colisiones son posibles en principio. La resistencia a encontrarlas es una propiedad de seguridad del algoritmo; no significa que dos hashes diferentes sean una prueba de autenticidad. NIST FIPS 180-4 define SHA-256 y sus usos para comprobar integridad.
Guarda este ejemplo como sha256.mjs y ejecútalo con node sha256.mjs:
import { createHash } from 'node:crypto';
function sha256(texto) {
return createHash('sha256').update(texto, 'utf8').digest('hex');
}
const original = 'Hola, uncodigo';
console.log(sha256(original));
console.log(sha256(original) === sha256(original)); // true
console.log(sha256(original) === sha256(original + '!')); // falseEl primer resultado contiene 64 caracteres hexadecimales: cada par representa un byte de los 32 bytes del resumen. Las dos comparaciones muestran que el resultado es reproducible y sensible al cambio probado. La API createHash de Node.js calcula el resumen; digest('hex') solo decide cómo mostrarlo.
No existe una función deshacerSHA256(hash). Sin embargo, si el original pertenece a un conjunto pequeño —por ejemplo, un código de cuatro dígitos o una contraseña común—, un atacante puede calcular los hashes de candidatos y buscar coincidencias. Además, si un atacante puede modificar tanto un archivo como el hash publicado junto a él, puede volver a calcular la huella. Para autenticidad frente a un adversario se necesita un mecanismo con secreto, como un MAC, o una firma digital, según el caso; SHA-256 por sí solo no aporta esa garantía.
Cifrado con AES-256-GCM: proteger y poder recuperar
AES es un cifrador simétrico: se cifra y descifra con la misma clave secreta. En AES-256-GCM, 256 indica el tamaño de la clave en bits y GCM es un modo de cifrado autenticado. Además del texto cifrado, produce una etiqueta de autenticación (tag); al descifrar, esta permite detectar cambios en el mensaje o en los parámetros autenticados. NIST SP 800-38D especifica GCM.
Guarda este ejemplo como aes-gcm.mjs y ejecútalo con node aes-gcm.mjs:
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
const clave = randomBytes(32); // 256 bits; efímera solo para este laboratorio
const iv = randomBytes(12); // 96 bits; nuevo para cada cifrado con esta clave
const mensaje = Buffer.from('Dato privado de prueba', 'utf8');
const cifrador = createCipheriv('aes-256-gcm', clave, iv, {
authTagLength: 16,
});
const cifrado = Buffer.concat([cifrador.update(mensaje), cifrador.final()]);
const tag = cifrador.getAuthTag();
// Estos tres valores se pueden guardar o transmitir con el mensaje cifrado.
const paquete = {
iv: iv.toString('base64'),
cifrado: cifrado.toString('base64'),
tag: tag.toString('base64'),
};
console.log(paquete);
const descifrador = createDecipheriv(
'aes-256-gcm', clave, Buffer.from(paquete.iv, 'base64'),
{ authTagLength: 16 },
);
descifrador.setAuthTag(Buffer.from(paquete.tag, 'base64'));
const recuperado = Buffer.concat([
descifrador.update(Buffer.from(paquete.cifrado, 'base64')),
descifrador.final(), // aquí se comprueba la etiqueta
]);
console.log(recuperado.toString('utf8'));
// Cambiar un byte invalida la etiqueta: no uses el resultado de update().
const alterado = Buffer.from(paquete.cifrado, 'base64');
alterado[0] ^= 1;
const verificador = createDecipheriv('aes-256-gcm', clave, iv, {
authTagLength: 16,
});
verificador.setAuthTag(tag);
try {
Buffer.concat([verificador.update(alterado), verificador.final()]);
console.log('Error: se aceptó un mensaje alterado');
} catch {
console.log('Alteración detectada');
}El texto recuperado debe ser Dato privado de prueba. Las cadenas Base64 del paquete no aportan seguridad: solo permiten transportar bytes como texto. El iv no es una contraseña y puede acompañar al cifrado; no debe repetirse con la misma clave. La clave sí debe permanecer secreta y disponible para descifrar después. Aquí se genera en memoria para la demostración: al terminar el proceso se pierde, de modo que este ejemplo no sirve como sistema de almacenamiento persistente. En una aplicación real, la clave requiere gestión segura y separada del dato cifrado. Node.js documenta los IV y la API de cifrado autenticado; OWASP ofrece recomendaciones de almacenamiento criptográfico.
El último bloque modifica un byte y debe mostrar Alteración detectada. Si alteras el texto cifrado, usas otra clave o entregas un tag incorrecto, la llamada a final() falla. No uses ningún dato obtenido de update() antes de que final() termine correctamente. Node.js indica que debe descartarse el texto cuando falla la autenticación. Este comportamiento detecta modificaciones; no demuestra quién envió el mensaje si varias personas conocen la misma clave.
NOTE
Los 12 bytes del IV y los 16 bytes del tag son decisiones del ejemplo acordes con GCM. La unicidad del IV por clave es esencial. En sistemas de mucho volumen o con varios procesos, diseña y revisa la estrategia de IV y rotación de claves; no reutilices sin más este ejemplo de laboratorio.
Codificación con Base64: cambiar la representación
Base64 transforma bytes en caracteres de un alfabeto definido por RFC 4648. Es útil cuando un canal espera texto, como en el paquete del ejemplo anterior. Cualquier persona que vea el valor puede decodificarlo, porque no hay clave ni protección criptográfica.
Guarda este ejemplo como base64.mjs y ejecútalo con node base64.mjs:
const original = 'Hola, uncodigo 👋';
const codificado = Buffer.from(original, 'utf8').toString('base64');
const recuperado = Buffer.from(codificado, 'base64').toString('utf8');
console.log(codificado);
console.log(recuperado); // Hola, uncodigo 👋El segundo Buffer.from recupera los bytes y UTF-8 vuelve a interpretarlos como texto. La documentación de Buffer de Node.js describe ambas conversiones. Puedes usar Base64 para representar un hash o un cifrado, pero codificar una contraseña en Base64 no la protege.
Por qué SHA-256 solo no sirve para guardar contraseñas
Una contraseña suele tener mucha menos entropía que una clave aleatoria. SHA-256 está diseñado para calcularse con rapidez; si se filtra una base de hashes, esa rapidez permite probar muchos candidatos. Añadir una sal única y aleatoria por contraseña evita que dos contraseñas iguales produzcan el mismo valor almacenado y dificulta tablas precalculadas, pero SHA-256 más sal sigue siendo demasiado rápido para este uso.
La guía de OWASP sobre almacenamiento de contraseñas recomienda funciones dedicadas y configurables, como Argon2id; propone scrypt cuando Argon2id no esté disponible y contempla PBKDF2 en contextos con requisitos FIPS. Estas funciones incorporan un coste de cálculo y deben utilizar una sal única. Al iniciar sesión, el sistema vuelve a aplicar la función al candidato y compara el resultado almacenado; no descifra la contraseña. La elección de parámetros requiere medir el rendimiento en el entorno real. Esto es una recomendación de diseño, no una obligación legal universal.
Errores frecuentes y cómo evitarlos
- Guardar secretos en Base64: quien tenga el valor puede decodificarlo. Usa cifrado autenticado si necesitas recuperarlos o un mecanismo específico de contraseñas si solo debes verificarlas.
- Confundir hash con autenticación: un hash público detecta cambios únicamente si confías en la huella de referencia. Para verificar procedencia frente a un atacante, usa un MAC o una firma según la relación entre las partes.
- Usar SHA-256 directo para contraseñas: elige una función de almacenamiento de contraseñas con sal y coste ajustable, siguiendo la recomendación de OWASP.
- Reutilizar IV con la misma clave en GCM: genera o administra IV únicos para cada cifrado. La unicidad es un requisito técnico del modo GCM descrito por NIST, no un detalle opcional del ejemplo.
- Exponer la clave o ignorar el
tag: no almacenes la clave junto al dato cifrado como si fuera pública; verifica la etiqueta y descarta el resultado si falla. No confundas el IV, que puede ser público, con la clave. - Derivar una clave AES de una contraseña con una conversión directa: una contraseña humana no se convierte en clave segura por truncarla o pasarle SHA-256 una vez. Si debes usar una contraseña para cifrar, necesitas una función de derivación de claves adecuada, una sal y parámetros revisados; OWASP distingue estas decisiones.
Elige la técnica según lo que necesitas proteger
Antes de transformar un dato, define qué necesitas hacer después con él. Para una huella reproducible de contenido, considera un hash como SHA-256 y protege la referencia si hay un adversario. Para recuperar un dato secreto, usa cifrado autenticado y gestiona la clave. Para pasar bytes por un canal textual, usa Base64 sabiendo que cualquiera puede deshacerlo. Y para contraseñas, usa una función diseñada para almacenarlas: ninguna de las tres demostraciones anteriores, por sí sola, sustituye esa tarea.
Referencias
- NIST FIPS 180-4: Secure Hash Standard — definición de SHA-256.
- NIST SP 800-38D: GCM y GMAC — cifrado autenticado y requisitos del modo GCM.
- RFC 4648: Base16, Base32 and Base64 Data Encodings — especificación de Base64.
- Node.js: módulo
node:crypto— uso y comportamiento de hash, cifrado, IV y etiquetas de autenticación. - Node.js: módulo
Buffer— conversión entre bytes, UTF-8 y Base64. - OWASP: Password Storage Cheat Sheet — recomendaciones para contraseñas.
- OWASP: Cryptographic Storage Cheat Sheet — recomendaciones para proteger datos recuperables y gestionar claves.
