FundamentosLección 04
Criptografía para desarrolladores
Cifrado simétrico y asimétrico, MAC y firmas digitales, TLS y gestión de claves. Lo que necesitas para usar la criptografía bien, sin inventarla.
Al terminar sabrás
- Elegir entre cifrado simétrico, asimétrico, MAC y firma según el problema.
- Cifrar y descifrar con AES-GCM en Java sin errores clásicos (ECB, nonce repetido).
- Entender qué garantiza TLS y qué papel juegan los certificados.
- Aplicar reglas básicas de gestión de claves y aleatoriedad.
La primera regla de la criptografía aplicada es no inventar criptografía. La segunda es saber qué pieza ya hecha usar para cada problema. Esta lección es un mapa: qué hace cada primitiva, cuándo usarla y cómo no romperla al usarla.
Simétrica y asimétrica
Sección titulada «Simétrica y asimétrica»- Simétrica (AES, ChaCha20): rapidísima, ideal para cifrar datos. El problema es compartir la clave.
- Asimétrica (RSA, curvas elípticas como ECDH/ECDSA/Ed25519): un par de claves. La pública se puede publicar; la privada nunca sale de su dueño. Es lenta, así que se usa para acordar claves y firmar, no para cifrar grandes volúmenes.
En la práctica se combinan: la asimétrica sirve para acordar una clave simétrica de sesión, y la simétrica cifra todo lo demás. Eso es exactamente lo que hace TLS.
Integridad y autenticidad: hash, MAC y firma
Sección titulada «Integridad y autenticidad: hash, MAC y firma»Cifrar oculta un mensaje, pero no impide que alguien lo modifique. Para detectar cambios hay tres herramientas, y la diferencia está en quién puede generar la prueba y quién puede verificarla:
| Herramienta | Quién la genera | Quién la verifica | Prueba | Uso típico |
|---|---|---|---|---|
| Hash (SHA-256) | Cualquiera | Cualquiera | Que el contenido no cambió si el hash viaja por un canal fiable | Checksums de descargas |
| MAC (HMAC-SHA256) | Quien tiene la clave | Quien tiene la misma clave | Integridad + que lo generó alguien con la clave | Webhooks, JWT HS256, cookies firmadas |
| Firma (RSA, ECDSA, Ed25519) | Solo el dueño de la privada | Cualquiera con la pública | Integridad + autoría (no repudio) | JWT RS256, certificados, actualizaciones de software |
Cifrado simétrico bien hecho: AES-GCM
Sección titulada «Cifrado simétrico bien hecho: AES-GCM»Hoy el modo por defecto es AES-GCM, un cifrado autenticado (AEAD): cifra y, a la vez, añade un tag que detecta cualquier modificación. Si alguien cambia un solo bit, descifrar falla en lugar de devolver basura.
private static final SecureRandom RANDOM = new SecureRandom();
static byte[] encrypt(SecretKey key, byte[] plaintext) throws GeneralSecurityException { byte[] iv = new byte[12]; // nonce de 96 bits RANDOM.nextBytes(iv); // ¡nuevo en CADA cifrado! Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv)); byte[] ciphertext = cipher.doFinal(plaintext); // incluye el tag de 128 bits return ByteBuffer.allocate(iv.length + ciphertext.length).put(iv).put(ciphertext).array();}
static byte[] decrypt(SecretKey key, byte[] message) throws GeneralSecurityException { ByteBuffer buf = ByteBuffer.wrap(message); byte[] iv = new byte[12]; buf.get(iv); byte[] ciphertext = new byte[buf.remaining()]; buf.get(ciphertext); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv)); return cipher.doFinal(ciphertext); // lanza AEADBadTagException si se manipuló}Y la clave se genera, nunca se inventa:
KeyGenerator generator = KeyGenerator.getInstance("AES");generator.init(256);SecretKey key = generator.generateKey();Firmas digitales
Sección titulada «Firmas digitales»KeyPair pair = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
Signature signer = Signature.getInstance("Ed25519");signer.initSign(pair.getPrivate());signer.update(document);byte[] signature = signer.sign();
Signature verifier = Signature.getInstance("Ed25519");verifier.initVerify(pair.getPublic());verifier.update(document);boolean valid = verifier.verify(signature); // false si cambió un solo byteTLS: lo que realmente protege HTTPS
Sección titulada «TLS: lo que realmente protege HTTPS»TLS crea un canal cifrado y autenticado entre el cliente y el servidor. Un resumen de TLS 1.3:
TLS garantiza confidencialidad e integridad en tránsito y autentica al servidor (gracias al certificado). Lo que no hace:
- No protege los datos en reposo (en tu base de datos o en tus logs).
- No autentica al usuario: para eso están la contraseña, el token o la sesión. (Salvo que uses mTLS, donde el cliente también presenta certificado; es común entre microservicios.)
- No te protege si el propio cliente está comprometido, ni de lo que tu aplicación haga mal.
Java 24 y 25 añaden al JDK la derivación de claves con HKDF (KDF), los algoritmos post-cuánticos ML-KEM y ML-DSA y, como preview, la lectura de claves PEM: lo verás en Java 25 y la seguridad.
Gestión de claves y aleatoriedad
Sección titulada «Gestión de claves y aleatoriedad»La criptografía traslada el problema de proteger los datos al de proteger las claves:
- Nunca en el código ni en el repositorio. Variables de entorno como mínimo; mejor un gestor de secretos o un KMS (AWS KMS, OCI Vault, HashiCorp Vault, Azure Key Vault).
- Rotación planificada: identifica cada clave (
kid) para poder publicar la nueva antes de retirar la vieja. - Separación: una clave por propósito (firmar tokens, cifrar datos, firmar cookies).
- Aleatoriedad criptográfica: tokens, sales, IVs y claves salen de
SecureRandom(Java) ocrypto.getRandomValues(navegador). Nunca deRandomni deMath.random, que son predecibles.
Evita
// Predecible: un atacante puede reconstruir la secuenciaString resetToken = Long.toHexString(new Random().nextLong());Prefiere
byte[] bytes = new byte[32];new SecureRandom().nextBytes(bytes);String resetToken = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);Mini lab · 15 min
Manipula un mensaje cifrado
Objetivo: Comprobar que AES-GCM detecta cualquier modificación y elegir la primitiva correcta para cada caso.
- Copia
encryptydecryptde arriba. Cifra el texto"saldo=100", cambia un bit del resultado (por ejemplomessage[20] ^= 1) e intenta descifrarlo. ¿Qué pasa? - Cifra dos veces el mismo texto con la misma clave. ¿Los resultados son iguales? ¿Por qué es importante?
- Elige la primitiva para cada caso: (a) guardar el IBAN de un cliente para cobrarle cada mes; (b) que tus microservicios verifiquen tokens emitidos por un servidor central; (c) verificar que un webhook viene de tu proveedor de pagos, con quien compartes un secreto; (d) guardar contraseñas.
Pista
Para el punto 3, pregúntate en cada caso: ¿necesito recuperar el dato original? ¿Quién debe poder generar la prueba y quién solo verificarla?
Mostrar respuestaOcultar respuesta
doFinallanzaAEADBadTagException: el tag de autenticación ya no cuadra. GCM no devuelve un texto alterado; se niega a descifrar. Eso es lo que significa “cifrado autenticado”.- Son distintos, porque cada cifrado usa un IV aleatorio. Si fueran iguales, un observador sabría cuándo se repite un mensaje (por ejemplo, “saldo=100” otra vez) sin necesidad de descifrarlo.
- (a) Cifrado simétrico (AES-GCM) con la clave en un KMS: necesitas recuperar el dato. (b) Firma asimétrica (RS256/ES256/EdDSA): los servicios verifican con la clave pública y no pueden fabricar tokens. (c) HMAC con el secreto compartido y comparación en tiempo constante. (d) Hash de contraseñas (Argon2id/bcrypt): nunca necesitas recuperarlas.