A través del enlace de un amigo he acabado en una página dónde se muestra a los votantes del referéndum catalán la ubicación de la urna dónde se debe votar. Como de costumbre, me ha podido la curiosidad y me he puesto a analizar como funcionaba el sistema. La verdad es que usan un sistema bastante interesante aunque el sistema criptográfico empleado presenta algunas vulnerabilidades. En este post os contaré tanto el funcionamiento como las vulnerabilidades del sistema.
A alto nivel el funcionamiento del sistema es bastante sencillo, se le pide al votante su número de DNI, su fecha de nacimiento y su código postal y se usan estos datos para identificar y autenticar al mismo. Un sistema criptográfico procesa dichas entradas y devuelve un identificador que se usa para decidir que fichero de la base de datos descargar, dicho identificador también se usa para encontrar la entrada específica del votante y para descifrar los datos que le indican la mesa electoral en la que ha de votar. Finalmente se le muestran los datos descifrados al votante.
En más detalle el funcionamiento tampoco es mucho más complicado. Se concatenan los últimos 5 dígitos del DNI, la letra, la fecha de nacimiento en formato AAAAMMDD y el código postal (usando 5 dígitos). Luego se calcula el hash SHA-256 de estos datos 1715 veces con el detalle de que el resultado es convertido a hexadecimal antes de ser hasheado, esto se usa como semilla para la clave de descifrado, dicha semilla es convertida a hexadecimal y hasheada de nuevo para obtener el identificador en la base de datos. Respecto a la clave de descifrado que se obtiene de dicha semilla, tanto la clave AES256 como el IV se obtienen usando una función equivalente a la función EVP_BytesToKey de openssl. Una vez obtenido el identificador se usa el primer byte en hexadecimal del hash para elegir el directorio y el segundo para elegir el archivo dentro de dicho directorio, dicho archivo se baja del servidor y se procesa línea a línea hasta encontrar una cuyos primeros 60 bytes coincidan con el resto de bytes en hexadecimal del identificador. Finalmente la clave y el IV obtenidos con la función equivalente a EVP_BytesToKey se usan para descifrar la información usando AES-256 en modo CBC el padding utilizado es PKCS#7. Los datos descifrados son luego procesados y mostrados al usuario. Si no aparece ninguna entrada para los datos introducidos, también se indica esto al usuario.
Todo este código está escrito en Javascript y está en la función calcula de la propia página web y en el módulo /js/bundleV8.js de dicha página. No está ofuscado lo que ha simplificado el proceso.
La verdad es que el sistema en si funciona de forma bastante rara, el cifrado de los datos de la mesa electoral no ofrece ninguna ventaja a la hora de proteger los datos del votante, ya que si un atacante obtiene el propio identificador de la entrada debería haber obtenido primero la semilla usada para cifrar los datos. Desde este punto de vista sería más eficiente, por ejemplo, asociar un identificador numérico a cada mesa y usar este en su lugar. Uno podría pensar que la idea es evitar que el atacante conozca todas las mesas, sin embargo se podría haber obtenido un resultado similar cifrando todas las mesas (incluyendo algunas falsas) y cifrando únicamente el identificador de la misma (o la clave de descifrado con la que se podría derivar el identificador). Además, el sistema no evita completamente el ataque, dado que es posible utilizar fuerza bruta y esta es trivialmente paralelizable. De hecho, es razonable pensar que la información que utiliza la base de datos (el DNI, la fecha de nacimiento y el código postal) ya la tenga el gobierno español y este pueda utilizarla para obtener la lista de mesas electorales.
Pero nosotros no somos el Gobierno Español, así que ¿cuales son nuestras posibilidades de obtener la información?. El DNI ofrece unos 21.13 bits de entropía, la fecha de nacimiento unos 15.53 y el código postal unos 15.80 para un total de unos 52.47 bits. Si elegimos únicamente los códigos postales de las provincias de Cataluña se nos quedan 11.55 bits para un total de 48.22. Es bastante obvio que es muy sencillo obtener un dato dados los otros dos (el peor caso sería el DNI que no llevaría algo más de 2²⁰ intentos), de forma semejante con sólo uno de los datos es factible obtener los otros dos aunque el coste es mayor (2³⁶ intentos en el peor caso) y obviamente el coste de atacar la base de datos completa por fuerza bruta es de algo menos de 2⁵³.
En aras de hacer pruebas he escrito una implementación en python (con una versión en C del algoritmo de hasheo) a modo de prueba de concepto. Con esta soy capaz de obtener unos 250 hashes por segundo con un Core 2 Duo y un sólo hilo usando sólo python y 500 usando el hasheo en C. Esta implementación no hace uso de instrucciones SIMD para calcular varios hashes a la vez ni de múltiples hilos y usa la implementación de SHA256 de openssl (o la de python si el módulo de C no está disponible), tampoco está muy optimizada. 500 hashes por segundo es muy poco, de hecho crackear la BBDD entera nos llevaría 395610 años. Sin embargo, una implementación optimizada para GPU debería poder llegar sin mucho problema a un millón de hashes por segundo (usando por ejemplo los kernels OpenCL de hashcat) esto reduce el tiempo de búsqueda a unos 198 años de GPU, o unos 86700$ si usáramos instancias de GPU de Amazon, caro desde luego, pero dentro del ámbito de lo factible.
Si quieres hacer pruebas, la base de datos completa pesa cerca de 2,3GB y puede bajarse con el siguiente comando:
curl --http2 --create-dirs -o '#1#2/#3#4' 'https://<página web>/db/{0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}{0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}/{0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}{0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}.db'
Por último, a modo de demostración, os dejo los datos de la mesa electoral de Roger Junqueras, el hermano de Oriol Junqueras. El DNI lo he obtenido utilizando Google y el código postal a través de las páginas blancas. Por cierto, su fecha de nacimiento es el 10 de Octubre de 1974 😉
COL.LEGI PUBLIC REIXAC#Pa\xc3\xafsos Catalans, s/n#MONTCADA I REIXAC#2#3#0A#e026f9484a40180141abc4f1793df6d8f3da736bf15224bcb3f74d41d3319074f20898f975211c6cca722708c313c14c41
De toda la información de la mesa, lo único que no tengo claro es para que sirve el último identificador en hexadecimal. Sospecho que pueda ser una firma para verificar la integridad de la clave pero 768 bits son muy pocos para una clave RSA y demasiados para una curva elíptica, así que no os se decir para que sirven.
haha son unos cachondos, calcula el hash SHA-256 de 1714 (+ el 0 claro) xD
PD: visca catalunya
Eso explica lo de que el bucle de hasheo se ejecute 1714 veces, la verdad es que me preguntaba a qué se debía el número.
HOla! SE puede obtener una lista completa de locales donde se pondrán mesas electorales?
El Gobierno puede hacerlo, tú sólo si estás dispuesto a pagar por las suficientes GPU para hacer un ataque de fuerza bruta.
Hola,
Se me ocurre que como utiliza SHA256 se podría usar una FPGA de las de minar bitcoins para hacer el cálculo de los hashes.
Con una de éstas https://www.buybitcoinworldwide.com/mining/hardware/antminer-s9/ si no he calculado mal se podría reducir el cálculo de hashing a 7 minutos. (a 14TH/s)
Saludos
La FPGA tendría que modificarse. El problema es que el algoritmo de minado de bitcoins hace SHA256(SHA256(bloque)) pero el algoritmo de la generalitat hace iteraciones en forma de HEX(SHA256(HEX(SHA256(datos)))) al convertir los datos a hexadecimal tras cada aplicación del hash sería necesario modificar el diseño de la FPGA para hacer dicha conversión.
Dicho esto los números me quedan en unas 20 horas y media:
100000(números DNI)*23(letras)*365(días en un año)*128(años, con suficiente margen)*11150(códigos postales españoles)*1716(iteraciones de hash)/28000000000000(14TH/s dobles)
A eso habría que añadirle la conversión a hexadecimal intermedia y la lógica para validar si el valor obtenido estaba o no en la BBDD. Esta última es más difícil de implementar por hardware.
Si quieres sólo códigos postales catalanes cambia el 11150 por 1146, te queda en poco más de dos horas en tal caso.
EDITADO: El número total de códigos postales era incorrecto y se ha corregido en los cálculos.
No entiendo lo de los 128 años. La gente entre 108 a 128 debe ser cero o ninguna. Es mas, yo dejaría de contar a partir de 83 y empezaría en 18 es decir en vez de multiplicar por 128 lo haría por 65 es decir casi he reducido los cálculos a la mitad. Y quizás se pierda un 2% de censo siendo muy pesimistas.
Por otro lado los códigos postales si quitamos los 900 o 100 menos poblados, pe 950 nos quedan 1146 -950= 196 con entre un 80 o 90 % de la población que además debido al envejecimiento de los pueblos no se sumara directamente con lo anterior. Es decir bajamos un 83% el tiempo necesario. Cojamos unos pocos códigos más hasta el 80%.
Es decir necesitamos un 50% de 20% de 2 horas es decir unos 12 minutos. Una gran seguridad.
Hola, las señoras Ana María Vela Rubio y Magdalena Oliver Gabarro son dos residentes de Cataluña con 115 y 113 años respectivamente. Por supuesto, puedes coger y decir que te da igual perder tantas entradas para decir que el ataque tardará menos pero entonces no comprometes los datos de todo el censo que es de lo que estoy hablando. Por no decir que para hacer un ataque basado en FPGA necesitarías modificar la FPGA, eso no es algo trivial que puedas hacer en dos minutos, las cifras de 28TH/s que ves son el resultado de varios años de mejoras iterativas enfocadas en la resolución de un problema muy concreto: SHA256(SHA256(x)) < y. Para atacar la base de datos del censo tendrías que o bien cargar los hashes en memoria (con los cerca de 170 MB que eso cuesta) y comparar uno a uno, o usar estructuras de datos más avanzadas como un filtro BLOOM y/o una tabla de hash enlazada que son más difíciles de implementar. A todo esto hay que añadirle si el valor de los datos merece el esfuerzo realizado.