← Todos los artículos

Compilaciones reproducibles: demostrando que el software que se ejecuta es el software que inspeccionaste

Suiza publicó el código fuente de su sistema de votación para que cualquiera pudiera inspeccionarlo — e inmediatamente investigadores encontraron una puerta trasera oculta que podría crear una prueba perfectamente convincente mientras cambiaba silenciosamente los votos.

Es marzo de 2019, y en Berna, Swiss Post está haciendo algo que suena como el estándar de oro de la transparencia electoral. Su sistema de votación por internet — construido por el proveedor español Scytl, destinado al uso en votaciones federales suizas vinculantes — está a punto de tener su código fuente completo publicado en línea para que el mundo lo lea.

Esto es lo que los defensores de la democracia habían estado exigiendo durante años. Abre la caja negra. Deja que los expertos miren. Confía a través de la inspección.

En cuestión de semanas, tres criptógrafos independientes — Sarah Jamie Lewis, Olivier Pereira, y Vanessa Teague — habían mirado. Y lo que encontraron no fue reconfortante.


La trampa que nadie vio hasta que alguien miró

El centro del sistema era una prueba matemática. Después de que todos los votos fueron mezclados y barajados para preservar el anonimato, el software generaría una "prueba de mezcla" criptográfica — una pieza de matemática que supuestamente demostraba, a cualquiera que la verificara, que la mezcla se había realizado correctamente y que ningún voto había sido alterado.

Se llamaba verificabilidad universal. La idea: no tienes que confiar en el servidor. Verificas la prueba tú mismo.

Los investigadores descubrieron que la prueba se basaba en algo llamado esquema de compromiso con puerta trasera. En lenguaje sencillo: la prueba de mezcla podría hacer que "verificara" correctamente incluso si los votos habían sido cambiados silenciosamente — siempre que supieras el valor de la puerta trasera. Y el valor de la puerta trasera era mantenido por los operadores del sistema.

En otras palabras, una autoridad que conociera ese valor podría generar una transcripción de prueba de mezcla que pasara la verificación mientras había reorganizado cada una de las papeletas. La afirmación de "verificabilidad universal" era infalsable desde el exterior.

Nombraron su artículo "Ceci n'est pas une preuve." Esto no es una prueba.

Swiss Post y Scytl reconocieron el hallazgo. Las autoridades suizas suspendieron el sistema.

La lección suena técnica. No lo es. El defecto fue invisible mientras el código estuvo cerrado. Se volvió identificable en el momento en que fue abierto. Pero encontrarlo aún requería criptógrafos de clase mundial trabajando durante semanas. La pregunta más amplia — la que este artículo realmente trata — es: ¿qué sucede entre "alguien puede leer el código" y "puedes estar seguro de que el código que fue leído es el que se ejecutó en la máquina"?

El caso suizo abrió el código. Pero expuso una brecha más profunda que el código abierto por sí solo no cierra automáticamente.


Leer la receta no es lo mismo que probar el plato

Imagina un restaurante que publica sus recetas. Cada ingrediente, cada técnica, cada paso — todo en línea, todo el tiempo.

Ahora imagina que la cocina está cerrada. Puedes leer lo que se supone que debe suceder. No puedes ver lo que sucede realmente.

Esta es precisamente la situación con la mayoría del software de votación, incluso el software de votación de código abierto. Publicar código fuente es una condición necesaria para la verificación independiente. No es una condición suficiente.

Aquí está el porqué. El software que lees en un repositorio no es el software que se ejecuta en la máquina. Entre "código fuente" y "programa en ejecución" hay una cadena de pasos:

  1. Un compilador toma el código fuente y lo traduce a código binario legible por máquina.
  2. Ese binario se empaqueta, se instala en hardware o servidores de votación, y se sella criptográficamente — o no.
  3. En el momento de la elección, una máquina se inicia y ejecuta lo que hay en ella.

Cada uno de esos pasos es un lugar donde el código en ejecución puede divergir del código fuente que inspeccionaste, sin evidencia visible. Un compilador puede ser manipulado para insertar código que no aparece en el código fuente. Un binario puede ser intercambiado después de ser construido. Una máquina puede iniciar una versión diferente a la que creías que estaba cargada.

La puerta trasera suiza vivía en el código fuente. Pero una puerta trasera diferente podría vivir en cualquier parte de esa cadena — y nunca aparecer en el código en absoluto.


El problema de las compilaciones reproducibles, en lenguaje sencillo

Hay una disciplina en la ingeniería de software llamada compilaciones reproducibles. Su objetivo es simple: dado el mismo código fuente, el mismo compilador, y las mismas instrucciones de compilación, cualquiera que ejecute el proceso de compilación debe obtener un binario idéntico — byte a byte, bit a bit — cada vez.

Esto importa porque te da una manera de verificar. Si la autoridad electoral publica un hash binario — una huella digital única del software en la máquina — y si puedes reproducir independientemente ese binario exacto a partir del código fuente publicado, tienes una garantía criptográfica de que el código en la máquina es el código que leíste.

Si el binario no coincide, sabes que algo cambió entre el código fuente y la máquina. No sabes qué cambió. Pero sabes que debes preguntar.

Sin compilaciones reproducibles, la brecha entre "código fuente inspeccionado" y "software en ejecución" es invisible. No tienes herramientas para verificarlo. Vuelves a confiar en la autoridad que realizó la compilación.

El proyecto Reproducible Builds — un esfuerzo entre proyectos en la comunidad de ingeniería de software — ha documentado sistemáticamente cuán difícil es lograr esto y cuántas formas diferentes la cadena de compilación puede producir resultados diferentes del mismo código fuente. Los compiladores incrustan marcas de tiempo. Los enlazadores insertan variables de entorno. El orden de los archivos varía. Cada uno es un mecanismo por el cual dos compilaciones "idénticas" pueden producir binarios diferentes sin que nadie tenga la intención de cometer fraude.


Atestación: la segunda mitad del problema

Incluso si tu compilación es reproducible, un segundo problema permanece. La reproducibilidad prueba que alguien puede reconstruir el mismo binario a partir del código fuente. No prueba que el binario ahora ejecutándose en tu máquina de votación sea ese binario.

Aquí es donde entra la atestación.

La atestación es el proceso de firmar criptográficamente una afirmación: "Este dispositivo está ejecutando el binario de software X, compilado en el tiempo T, con hash H." La firma debe provenir de algo que sea evidente de manipulación — idealmente del hardware que no puede ser anulado por software después del arranque, como un Módulo de Plataforma de Confianza (TPM) o un módulo de seguridad de hardware. La afirmación firmada puede entonces ser verificada por cualquiera que tenga la clave pública.

Piénsalo como un sello evidente de manipulación en una tarjeta de memoria — excepto que en lugar de una pegatina física que se rompe cuando se abre, es una firma matemática que puedes verificar sin confiar en la persona que te entrega la tarjeta.

Juntas, las compilaciones reproducibles y la atestación forman una cadena: lees el código fuente → lo compilas y obtienes el binario H → verificas que el dispositivo en ejecución está atestiguando el binario H → estás confiado de que la máquina está ejecutando el código que leíste.

Sin esa cadena, tienes dos hechos desconectados: aquí está el código fuente y aquí está una máquina ejecutando algo. Si esas dos cosas corresponden o no es una cuestión de confianza, no de verificación.

"Ejecutamos software certificado y de código abierto" es una afirmación. Las compilaciones reproducibles y la atestación son lo que la hacen verificable.


Lo que el caso suizo nos dice sobre el problema más profundo

Vuelve a Suiza. Los investigadores encontraron la puerta trasera porque leyeron el código fuente. Swiss Post reconoció el defecto y suspendió el sistema. Hasta aquí: el sistema funcionando como se pretendía.

Pero fíjate en lo que no fue verificado en ninguna versión reportada de esa historia. Incluso si la puerta trasera no hubiera existido en el código fuente, ninguna parte independiente tenía un mecanismo para confirmar que el binario ejecutándose en los servidores de Swiss Post fue compilado a partir de ese código fuente exacto — y no a partir de una versión ligeramente modificada con un defecto que nunca apareció en el código público.

Eso no es hipotético. En 2003, el científico informático Ken Thompson, en su conferencia del Premio Turing, describió cómo un compilador podría ser modificado para insertar una puerta trasera en un programa automáticamente — y luego modificado aún más para insertar la puerta trasera en sí mismo, de modo que incluso compilar el compilador a partir de código limpio produciría un binario comprometido. El código fuente se ve pristino. El compilador se ve pristino. La salida está troyanizada.

Esto no es un ataque teórico exótico. Es una clase de amenaza bien entendida. Y la defensa contra ella son las compilaciones reproducibles: si la salida es determinista y publicada, cualquier parte independiente puede recompilar a partir del código fuente y detectar una discrepancia. Si no lo es — si cada compilación produce un binario diferente por razones técnicas inocentes — la comparación nunca puede hacerse.

El caso suizo probó que el código abierto es mejor que el código cerrado. También probó que el código abierto, solo, no es suficiente.


La brecha que nuestro rastreador de brechas está observando

En TrustVoting, rastreamos una brecha específica: la ausencia, en la mayoría de los sistemas de votación implementados, de compilaciones reproducibles verificadas públicamente y atestación de dispositivos. Puedes ver cuán frecuentemente aparece esta brecha, y dónde, en todo el mundo.

El sistema suizo tenía código abierto. Es uno de los programas de votación por internet más transparentes del mundo democrático. Los investigadores aún encontraron un defecto en la criptografía — que habría sido imposible de encontrar en un sistema cerrado. Y incluso después de que ese defecto fue encontrado y reparado, un observador independiente no tendría mecanismo para confirmar que el binario parcheado en el servidor fue derivado del código fuente parcheado.

Esta es la brecha. Se sienta precisamente en la costura entre el código que puedes leer y el software que realmente maneja tu voto.

Algunas cosas que la cerrarían:

  • Instrucciones de compilación publicadas públicamente que produzcan el hash binario exacto que la autoridad afirma estar ejecutando — para que cualquier observador suficientemente técnico pueda verificar de forma independiente.
  • Atestación firmada desde hardware evidente de manipulación en cada dispositivo de votación, publicando el hash del binario en ejecución junto con cada lote de resultados.
  • Monitoreo continuo y post-implementación contra el hash publicado — para que un intercambio de software que ocurra después de la certificación pero antes del día de las elecciones sea detectable.

Ninguno de estos es exótico. Son práctica estándar en la implementación de software de alta seguridad. Están en gran medida ausentes de los requisitos de certificación de sistemas de votación.


Lo que aún no puedes verificar hoy — y qué lo arreglería

Aquí es donde nos encontramos en la posición incómoda después de Suiza 2019.

Sabemos que el código abierto es mejor. La puerta trasera fue encontrada precisamente porque el código era legible. Un sistema cerrado habría enviado el defecto sin detectar.

Sabemos que la verificabilidad criptográfica es mejor que solo las papeletas de papel. La filosofía de diseño completo del sistema suizo — verificabilidad universal, pruebas matemáticamente verificables — es un intento de permitir que los votantes confirmen, sin confiar en la autoridad, que su voto fue contado.

Y sabemos que ambos son insuficientes si no incluyen el puente entre código fuente y binario en ejecución.

Ahora mismo, para virtualmente cada sistema de votación implementado en el mundo, la respuesta honesta a "¿cómo sé que el software en esa máquina es el software que fue revisado?" es: no puedes. Confías en la autoridad que lo implementó.

Esa confianza puede estar bien fundamentada. Pero la confianza no es verificación. La Corte Constitucional Federal de Alemania entendió esto cuando falló en 2009 que la votación electrónica solo es legítima cuando ciudadanos ordinarios — no solo expertos — pueden verificar independientemente cada paso esencial desde la papeleta hasta el resultado. El razonamiento de la corte se aplica tan agudamente a la implementación de software como a la cuenta de votos: si confirmar que el código correcto se está ejecutando requiere confiar en las personas que lo ejecutan, el paso de verificación esencial falta.

La solución no es derribar los esfuerzos de votación de código abierto. Es completarlos. Código abierto más compilaciones reproducibles más atestación firmada más hashes binarios publicados públicamente equivale a un sistema donde "el software que inspeccionaste es el software que se ejecutó" es verificable por cualquiera con una laptop y curiosidad — no solo por la autoridad que lo implementó.

Hasta que esa cadena se cierre, cada afirmación de "usamos software abierto y certificado" es una invitación a confiar. Y un sistema electoral que te pide que confíes en él, en lugar de mostrarte la prueba, no ha terminado de resolver el problema.

Ve cómo la brecha de compilaciones reproducibles aparece en los sistemas de votación que rastreamos

Lee la versión de 2 minutos de este argumento

Explora el mapa global de brechas de verificación de tecnología electoral


Fuentes