DEV Community

UacProgrammer
UacProgrammer

Posted on AI-assisted

¿Y si eliminamos todas las matemáticas de SHA-256? Mi experimento con Tablas de Búsqueda (LUT)

Todo empezó con una pregunta que sonaba tonta, de esas que uno se hace a las 3 a. m.: "¿Y si un hash no tuviera que calcular nada? ¿Y si los bits solo… se buscaran en una tabla?" La respuesta corta es que se puede. La respuesta larga es este artículo.

SHA-256 siempre se enseñó como aritmética: rotaciones, XOR, sumas módulo 2³², 64 rondas de un molinete de registros. Yo quería la versión opuesta: un SHA-256 donde el dato jamás se suma, jamás se rota, jamás se enmascara — solo se usa como índice de una tabla precomputada. Lo llamé LUT-SHA256.

El primer obstáculo fue la suma. Sumar dos números de 32 bits con una tabla directa necesitaría 2⁶⁴ entradas: imposible. La solución llegó descomponiendo la palabra en 4 bytes y encadenando el acarreo: una tabla TADDC(a, b, c) de 131.072 entradas que devuelve "suma de byte + acarreo saliente". Cuatro búsquedas encadenadas y ya está: suma módulo 2³², sin sumar nada. Las rotaciones se volvieron recombinación de bytes: rotar 6 bits = tomar medio byte de aquí, medio de allá y pegarlos con una tabla de desplazamiento. Y de pronto, las 64 rondas completas quedaron como una secuencia de ~14.000 búsquedas en memoria.

Lo más satisfactorio: escribí la implementación, la corrí contra hashlib y los digestos coincidieron en todos los escenarios — vectores FIPS, los ejercicios BIP39 con los que empecé este viaje, mensajes largos, fronteras de bloque. Había un teorema de equivalencia esperando a ser demostrado, y la inducción sobre la cadena de acarreos lo demostró.

El análisis de seguridad. Como el dato solo aparece como índice, mi propia implementación tenía una superficie de ataque distinta: la memoria observable. Construí entonces una "escalera de vulnerabilidad": versiones V1.0 a V1.7 que producen el mismo digesto que SHA-256, pero filtran un poquito más cada una. La traza completa de búsquedas (V1.1) regalaba el mensaje literalmente. Los acarreos (V1.2) no bastaban ni para Z3. Pero el hallazgo elegante fue el bus T1 (V1.4): una sola suma filtrada por ronda permitía reconstruir el mensaje algebraicamente, sin fuerza bruta, desenrollando la recurrencia de la ronda. Y con 15 fugas de 16, el digesto público completaba el hueco restante: Z3 lo resolvió en 68 segundos. Rompí mi propio hash — no tocando su matemática, que sigue intacta — sino leyendo lo que su implementación susurraba.

La realidad del hardware. ¿Sirve esto para minar Bitcoin en GPU? No. Los accesos a memoria dependientes del dato no coalescen; la GPU se muere de hambre. Pero en el mundo inverso — FPGAs llenas de BRAM, secure elements con ROM certificada, compute-in-memory — un hash que solo indexa tablas es oro: casi cero lógica, casi todo memoria, y un consumo ridículo. Y la variante v2.3, que reconoce bloques de ceros y se salta todo el programa W, es un regalo para PBKDF2/BIP39: las billeteras de hardware pasan la mitad de su vida hasheando relleno de ceros.

Conclusión. Lo que empezó como una idea espontánea terminó en cinco teoremas, seis compresiones verificadas y una escalera de vulnerabilidad documentada en un informe de 12 páginas. Si algo me llevo de esto es que los hashes no son "magia negra": son funciones, y toda función es, en el fondo, una tabla. La pregunta es quién se atreve a leerla.


Si te gustó este experimento y quieres apoyar más investigación independiente en criptografía y arquitectura de hardware, puedes dejarle un café (o un satoshi) a Abraham A. aquí: Bitcoin (SegWit): bc1qqgqyu462z3n6lduvahfuvpm0lp6rzpkxal92t9


¿Te interesa el rigor completo? El paper (formato IEEE), el informe técnico PDF v1.1 y el código están en el repositorio de LUT-SHA256.: https://github.com/UacProgrammer/LUT-SHA256

Top comments (0)