Una mirada práctica a las ideas de Daniel J. Bernstein sobre seguridad por construcción—de qmail a Curve25519—y qué significa en la práctica una criptografía “simple y verificable”.

Security-by-construction significa diseñar un sistema de modo que los errores comunes sean difíciles de cometer y que el daño de los errores inevitables esté limitado. En lugar de confiar en una larga lista de verificación (“recuerda validar X, sanear Y, configurar Z…”), diseñas el software para que el camino más seguro sea también el más sencillo.
Piénsalo como un embalaje a prueba de niños: no asume que todo el mundo será perfectamente cuidadoso; asume que las personas están cansadas, ocupadas y a veces se equivocan. Un buen diseño reduce cuánto “comportamiento perfecto” se requiere de desarrolladores, operadores y usuarios.
Los problemas de seguridad suelen esconderse en la complejidad: demasiadas funcionalidades, demasiadas opciones, demasiadas interacciones entre componentes. Cada control extra puede crear un nuevo modo de fallo: una forma inesperada en que el sistema puede romperse o ser usado mal.
La simplicidad ayuda de dos maneras prácticas:
Esto no se trata de minimalismo por el gusto de ser minimalista. Se trata de mantener el conjunto de comportamientos lo suficientemente pequeño como para que puedas entenderlo, probarlo y razonar sobre lo que pasa cuando algo falla.
Este artículo usa el trabajo de Daniel J. Bernstein como ejemplos concretos de security-by-construction: cómo qmail buscó reducir los modos de fallo, cómo el pensamiento en tiempo-constante evita fugas invisibles, y cómo Curve25519/X25519 y NaCl empujan hacia una criptografía más difícil de usar mal.
Lo que no hará: ofrecer una historia completa de la criptografía, probar formalmente algoritmos o afirmar que existe una única “mejor” librería para cada producto. Tampoco fingirá que buenos primitivos lo solucionan todo: los sistemas reales siguen fallando por manejo de llaves, errores de integración y fallos operacionales.
El objetivo es simple: mostrar patrones de diseño que hacen más probables resultados seguros, incluso si no eres especialista en criptografía.
Daniel J. Bernstein (a menudo “DJB”) es un matemático e informático cuyo trabajo aparece repetidamente en ingeniería de seguridad práctica: sistemas de correo (qmail), primitivos y protocolos criptográficos (notablemente Curve25519/X25519) y librerías que empaquetan criptografía para uso real (NaCl).
La gente cita a DJB no porque haya escrito la única “forma correcta” de hacer seguridad, sino porque sus proyectos comparten un conjunto consistente de instintos de ingeniería que reducen las formas en que algo puede salir mal.
Un tema recurrente es interfaces más pequeñas y más concretas. Si un sistema expone menos puntos de entrada y menos opciones de configuración, es más fácil de revisar, más fácil de probar y más difícil de usar mal accidentalmente.
Otro tema es suposiciones explícitas. Las fallas de seguridad suelen venir de expectativas no expresadas—sobre aleatoriedad, comportamiento temporal, manejo de errores o cómo se almacenan las claves. Los escritos e implementaciones de DJB tienden a hacer el modelo de amenazas concreto: qué se protege, de quién y bajo qué condiciones.
Finalmente, hay una inclinación hacia predeterminados más seguros y la corrección aburrida. Muchos diseños en esta tradición intentan eliminar filos cortantes que llevan a bugs sutiles: parámetros ambiguos, modos opcionales y atajos de rendimiento que filtran información.
Este artículo no es la historia de su vida ni un debate de personalidades. Es una lectura de ingeniería: qué patrones puedes observar en qmail, pensamiento en tiempo-constante, Curve25519/X25519 y NaCl, y cómo esos patrones se mapean a construir sistemas más simples de verificar y menos frágiles en producción.
qmail se construyó para resolver un problema poco glamuroso: entregar correo de forma fiable tratando al servidor de correo como un objetivo de alto valor. Los sistemas de correo están en internet, aceptan entradas hostiles todo el día y manejan datos sensibles (mensajes, credenciales, reglas de enrutamiento). Históricamente, un bug en un demonio de correo monolítico podía significar una toma total del sistema—o pérdida silenciosa de mensajes que nadie nota hasta que es tarde.
Una idea definitoria en qmail es partir la “entrega de correo” en pequeños programas que hacen una sola tarea: recibir, encolar, entrega local, entrega remota, etc. Cada pieza tiene una interfaz estrecha y responsabilidades limitadas.
Esa separación importa porque los fallos se vuelven locales:
Esto es security-by-construction en forma práctica: diseña el sistema para que “un error” sea menos probable que se convierta en “fallo total”.
qmail también modela hábitos que se traducen bien más allá del correo:
La conclusión no es “usa qmail”. Es que a menudo puedes lograr grandes mejoras de seguridad rediseñando alrededor de menos modos de fallo—antes de escribir más código o añadir más mandos.
“Superficie de ataque” es la suma de todos los lugares donde tu sistema puede ser pinchado, manipulado o engañado para hacer lo incorrecto. Una analogía útil es una casa: cada puerta, ventana, abridor de garaje, llave de repuesto y hueco de entrega es un punto de entrada potencial. Puedes poner mejores cerraduras, pero también es más seguro tener menos puntos de entrada en primer lugar.
El software es igual. Cada puerto que abres, formato de archivo que aceptas, endpoint de administración que expones, perilla de configuración que añades y gancho de plugin que soportas incrementa las formas en que las cosas pueden fallar.
Una “interfaz estrecha” es una API que hace menos, acepta menos variación y rechaza entradas ambiguas. A menudo parece restrictiva—pero es más fácil de asegurar porque hay menos caminos de código que auditar y menos interacciones sorprendentes.
Considera dos diseños:
El segundo diseño reduce lo que los atacantes pueden manipular. También reduce lo que tu equipo puede configurar mal accidentalmente.
Las opciones multiplican las pruebas. Si soportas 10 interruptores, no tienes 10 comportamientos—tienes combinaciones. Muchos bugs de seguridad viven en esas costuras: “esta bandera desactiva una comprobación”, “este modo salta la validación”, “esta configuración heredada elude límites de tasa”. Las interfaces estrechas convierten la “seguridad elige-tu-aventura” en un camino bien iluminado.
Usa esto para detectar superficie de ataque que crece silenciosamente:
Cuando no puedas reducir la interfaz, hazla estricta: valida pronto, rechaza campos desconocidos y deja las “funciones potentes” detrás de endpoints separados y claramente acotados.
El comportamiento “constant-time” significa que una operación tarda (aproximadamente) el mismo tiempo independientemente de valores secretos como claves privadas, nonces o bits intermedios. El objetivo no es ser rápido; es ser aburrido: si un atacante no puede correlacionar tiempo de ejecución con secretos, le será mucho más difícil extraer esos secretos por observación.
Las fugas por tiempo importan porque los atacantes no siempre necesitan romper las matemáticas. Si pueden ejecutar la misma operación muchas veces (o verla ejecutarse en hardware compartido), pequeñas diferencias—microsegundos, nanosegundos, incluso efectos de caché—pueden revelar patrones que, acumulados, llevan a recuperar claves.
Incluso el código “normal” puede comportarse de forma diferente según los datos:
if (secret_bit) { ... } cambia el flujo de control y a menudo el tiempo de ejecución.No necesitas leer ensamblador para sacar valor de una auditoría:
if dependientes de secretos, índices de arrays, bucles con terminación basada en secretos y lógica “camino rápido/camino lento”.El pensamiento en tiempo-constante es menos cuestión de heroicidades y más de disciplina: diseña código para que los secretos no puedan dirigir el timing en primer lugar.
El intercambio de claves en curva elíptica es una forma para que dos dispositivos creen el mismo secreto compartido aunque solo envíen mensajes “públicos” por la red. Cada parte genera un valor privado (guardado en secreto) y un valor público correspondiente (seguro de enviar). Tras intercambiar valores públicos, ambos combinan su propio valor privado con el público del otro para obtener un secreto compartido idéntico. Un eavesdropper ve los valores públicos pero no puede reconstruir de forma factible el secreto compartido, así que las dos partes derivan claves de cifrado y hablan en privado.
Curve25519 es la curva subyacente; X25519 es la función estandarizada y “haz esto específico” de intercambio de claves construida sobre ella. Su atractivo es, en gran parte, security-by-construction: menos disparadores, menos elecciones de parámetros y menos formas de escoger una configuración insegura.
También son rápidas en una amplia gama de hardware, lo que importa para servidores que manejan muchas conexiones y para teléfonos que quieren ahorrar batería. Y el diseño fomenta implementaciones que son más fáciles de mantener en tiempo-constante (ayudando a resistir ataques por tiempo), lo que reduce el riesgo de que un atacante ingenioso extraiga secretos midiendo pequeñas diferencias de rendimiento.
X25519 te da acuerdo de claves: ayuda a dos partes a derivar un secreto compartido para cifrado simétrico.
No proporciona autenticación por sí misma. Si ejecutas X25519 sin verificar quién es la otra parte (por ejemplo, con certificados, firmas o una clave precompartida), aún puedes ser engañado para hablar de forma segura con la parte equivocada. En otras palabras: X25519 ayuda a evitar el espionaje, pero no evita la suplantación por sí solo.
NaCl (la “Networking and Cryptography library”) se creó con un objetivo simple: dificultar que desarrolladores de aplicaciones ensamblen criptografía insegura por accidente. En lugar de ofrecer un buffet de algoritmos, modos, reglas de padding y perillas de configuración, NaCl te empuja hacia un pequeño conjunto de operaciones de alto nivel que ya están conectadas de forma segura.
Las APIs de NaCl se nombran según lo que quieres hacer, no por qué primitivo quieres componer.
crypto_box (“box”): cifrado autenticado con clave pública. Le das tu clave privada, la clave pública del destinatario, un nonce y un mensaje. Obtienes un ciphertext que (a) oculta el mensaje y (b) prueba que proviene de alguien que conoce la clave correcta.crypto_secretbox (“secretbox”): cifrado autenticado con clave compartida. La misma idea, pero con una única clave secreta compartida.El beneficio clave es que no eliges por separado “modo de cifrado” y “algoritmo MAC” y luego esperas haberlos combinado correctamente. Los predeterminados de NaCl imponen composiciones modernas y resistentes al mal uso (encrypt-then-authenticate), de modo que fallos comunes—como olvidar comprobaciones de integridad—son mucho menos probables.
La rigidez de NaCl puede sentirse limitante si necesitas compatibilidad con protocolos heredados, formatos especializados o algoritmos exigidos por regulación. Cambias “puedo ajustar cada parámetro” por “puedo enviar algo seguro sin ser experto en criptografía”.
Para muchos productos, ese es exactamente el punto: restringir el espacio de diseño para que haya menos bugs. Si de verdad necesitas personalización, puedes bajar a primitivos de bajo nivel—pero entonces te estás exponiendo de nuevo a los filos cortantes.
“Seguro por defecto” significa que la opción más razonable y segura es la que obtienes cuando no haces nada. Si un desarrollador instala una librería, copia un ejemplo rápido o usa los predeterminados del framework, el resultado debería ser difícil de usar mal y difícil de debilitar accidentalmente.
Los predeterminados importan porque la mayoría de los sistemas reales funcionan con ellos. Los equipos se mueven rápido, la documentación se lee por encima y la configuración crece de forma orgánica. Si el predeterminado es “flexible”, eso suele traducirse en “fácil de configurar mal”.
Los fallos criptográficos no siempre son causados por “matemáticas malas”. A menudo vienen de elegir una configuración peligrosa porque estaba disponible, familiar o era fácil.
Trampas comunes en los predeterminados incluyen:
Prefiere stacks que hagan el camino seguro el más fácil: primitivos revisados, parámetros conservadores y APIs que no te pidan tomar decisiones frágiles. Si una librería te fuerza a escoger entre diez algoritmos, cinco modos y múltiples codificaciones, te están pidiendo que hagas ingeniería de seguridad mediante configuración.
Cuando puedas, elige librerías y diseños que:
Security-by-construction es, en parte, negarse a convertir cada decisión en un menú desplegable.
“Verificable” no significa “formalmente probado” en la mayoría de los equipos de producto. Significa que puedes construir confianza de forma rápida, repetible y con menos oportunidades de malinterpretar lo que hace el código.
Un código es más verificable cuando:
Cada rama, modo y característica opcional multiplica lo que los revisores deben razonar. Interfaces más simples reducen el conjunto de estados posibles, lo que mejora la calidad de la revisión en dos formas:
Mantenlo aburrido y repetible:
Esta combinación no reemplaza la revisión experta, pero eleva el mínimo: menos sorpresas, detección más rápida y código que realmente puedes razonar.
Incluso si eliges primitivos bien considerados como X25519 o una API mínima estilo NaCl “box”/“secretbox”, los sistemas siguen rompiéndose en las partes desordenadas: integración, codificación y operaciones. La mayoría de los incidentes reales no son “las matemáticas estaban mal”, sino “se usaron mal las matemáticas”.
Errores en manejo de claves: reutilizar claves de larga duración donde se espera una clave efímera, almacenar claves en control de código fuente o confundir “clave pública” y “clave secreta” porque ambas son solo arrays de bytes.
Mal uso de nonces es reincidente. Muchos esquemas de cifrado autenticado requieren un nonce único por clave. Si duplicas un nonce (a menudo por reinicio de contador, carreras entre procesos o suposiciones de “aleatorio suficiente”), puedes perder confidencialidad o integridad.
Problemas de codificación y parseo crean fallos silenciosos: confusión base64 vs hex, pérdida de ceros iniciales, endianness inconsistente o aceptar múltiples codificaciones que comparan distinto. Estos bugs pueden convertir una “firma verificada” en “se verificó otra cosa”.
Manejo de errores puede ser peligroso en ambas direcciones: devolver errores detallados que ayudan al atacante, o ignorar fallos de verificación y seguir adelante.
Los secretos se filtran por logs, reportes de fallos, analytics y endpoints de “debug”. Las claves también acaban en backups, imágenes de VM y variables de entorno compartidas demasiado ampliamente. Mientras tanto, las actualizaciones de dependencias (o la falta de ellas) pueden dejarte con una implementación vulnerable aunque el diseño fuera sólido.
Buenos primitivos no producen automáticamente un producto seguro. Cuantas más elecciones expongas—modos, padding, codificaciones, “tweaks” personalizados—más maneras habrá de que los equipos construyan algo frágil por accidente. Un enfoque security-by-construction empieza por escoger una ruta de ingeniería que reduzca puntos de decisión.
Usa una librería de alto nivel (APIs one-shot como “cifra este mensaje para este destinatario”) cuando:
Compone primitivos de bajo nivel (AEADs, hashes, intercambio de claves) solo cuando:
Una regla útil: si tu documento de diseño contiene “elegiremos el modo más tarde” o “seremos cuidadosos con los nonces”, ya estás dejando demasiadas perillas.
Pide respuestas concretas, no marketing:
Trata la criptografía como código crítico para la seguridad: mantiene la superficie API pequeña, fija versiones, añade tests de respuestas conocidas y ejecuta fuzzing en parseos/serializaciones. Documenta lo que no soportarás (algoritmos, formatos heredados) y crea migraciones en lugar de “interruptores de compatibilidad” que perduran para siempre.
Security-by-construction no es una herramienta nueva que compras—es un conjunto de hábitos que hacen más difícil crear grandes categorías de bugs. El hilo común en la ingeniería estilo DJB es: mantén las cosas lo bastante simples como para razonarlas, haz interfaces lo bastante estrechas para limitar el mal uso, escribe código que se comporte igual aun bajo ataque y elige predeterminados que fallen a favor de la seguridad.
Si quieres una checklist estructurada para estos pasos, considera añadir una página interna de “inventario criptográfico” junto a tus docs de seguridad (por ejemplo, /security).
Estas ideas no se limitan a librerías criptográficas: se aplican a cómo construyes y despliegas software. Si usas un flujo de trabajo de vibe-coding (por ejemplo, Koder.ai, donde creas apps web/servidor/móviles vía chat), los mismos principios aparecen como restricciones de producto: mantener un número reducido de stacks soportados (React en web, Go + PostgreSQL en backend, Flutter en móvil), enfatizar la planificación antes de generar cambios y hacer que la reversión sea barata.
En la práctica, características como modo planificación, snapshots y rollback y exportación de código fuente ayudan a reducir el “radio de explosión” de errores: puedes revisar la intención antes de que los cambios aterricen, revertir rápido si algo va mal y verificar que lo que corre coincide con lo que se generó. Es el mismo instinto de security-by-construction que la compartimentación de qmail—aplicado a pipelines modernos de entrega.
Security-by-construction consiste en diseñar el software de modo que la ruta más segura sea también la más fácil. En lugar de confiar en que la gente recuerde largas listas de verificación, se limita el sistema para que los errores comunes sean difíciles de cometer y los errores inevitables tengan impacto reducido (un “radio de explosión” menor).
La complejidad genera interacciones ocultas y casos límite difíciles de probar y fáciles de configurar mal.
Beneficios prácticos de la simplicidad:
Una interfaz estrecha hace menos y acepta menos variación. Evita entradas ambiguas y reduce modos opcionales que crean “seguridad por configuración”.
Un enfoque práctico es:
qmail divide el manejo del correo en pequeños programas (recibir, encolar, entregar, etc.) con responsabilidades estrechas. Esto reduce los modos de fallo porque:
El comportamiento constante en el tiempo intenta que el tiempo de ejecución (y a menudo los patrones de acceso a memoria) no dependan de valores secretos. Esto importa porque los atacantes a veces pueden inferir secretos midiendo tiempo, efectos en caché o diferencias entre "camino rápido" y "camino lento" tras muchos ensayos.
Se trata de prevenir fugas “invisibles”, no solo de elegir algoritmos fuertes.
Empieza por identificar qué es secreto (claves privadas, secretos compartidos, claves de MAC, etiquetas de autenticación) y busca lugares donde los secretos influyan en flujo de control o accesos a memoria.
Señales de alarma:
if basadas en datos secretosTambién verifica que la dependencia criptográfica declare explícitamente comportamiento constante en el tiempo para las operaciones que necesitas.
X25519 es la función de intercambio de claves estandarizada construida sobre Curve25519. Se hizo popular porque reduce "disparadores": menos parámetros para elegir, buen rendimiento y un diseño que facilita implementaciones en tiempo constante.
Es una vía por defecto más segura para el intercambio de claves, siempre que gestiones la autenticación y el manejo de claves correctamente.
No. X25519 proporciona acuerdo de claves (un secreto compartido) pero no autentica con quién estás hablando.
Para evitar suplantaciones debes combinarlo con autenticación, por ejemplo:
Sin autenticación, puedes acabar “hablando de forma segura” con la parte equivocada.
NaCl reduce errores ofreciendo operaciones de alto nivel ya compuestas de forma segura, en lugar de exponer un buffet de algoritmos y modos.
Dos bloques comunes:
crypto_box: cifrado autenticado con clave pública (tus claves + claves del receptor + nonce → ciphertext)crypto_secretbox: cifrado autenticado con clave compartidaEl beneficio práctico es evitar errores de composición comunes (por ejemplo, cifrar sin protección de integridad).
Los buenos primitivos fallan cuando la integración y la operación son descuidadas. Fallos comunes:
Mitigaciones: