Estoy en una situación en la que necesito endurecer un hash de contraseña, pero no se me permite traer dependencias adicionales, así que estoy bastante obligado a hacer lo único que todos parecen desaconsejar: implementar mi propia implementación.
La familia SHA, considerada hash rápida, no parece adecuada para el hashing de contraseñas. Y también existe el riesgo de pérdida de entropía debido a los hashes repetidos de hash.
La intención de "hash lento" parece ser un aumento del tiempo de CPU y los requisitos de memoria, por lo que concibí la siguiente estrategia.
- la contraseña está oculta a través de SHA-3 512
- el valor de hash se envía a un PRNG mt19937 a través de una secuencia semilla
- se genera una larga secuencia aleatoria para rehacerse
- repetir el mismo número de veces n
- el valor de hash final se deriva del hash de todas las secuencias al revés
Eso parece introducir una cantidad extra configurable justa de trabajo y requisitos de memoria.
Entonces, mi pregunta es si esta es una estrategia razonable y, de ser así, qué longitud de secuencia y profundidad de recursión deberían ser suficientes.
Actualización:
Terminé expandiendo un poco la implementación, al incorporar también un algoritmo de hash en "aleatorio" para cada paso del proceso, 12 en total: SHA2, RealSHA3 y Keccak con un tamaño de resumen de 224, 256, 384 y 512. utilizando un doble fijo como selector.
En general, en comparación con un solo hash SHA3 simple, esta implementación es aproximadamente 100k veces más lenta, e incurre en un costo adicional de ~ 20 MB de RAM en una recursión bastante profunda y arbitrariamente ramificada para producir el hash final, que es lo que puede permitirse el lujo de permanecer dentro de límites razonables teniendo en cuenta las especificaciones mínimas de la plataforma de destino. Pero posiblemente igual de importante, esto brinda una diversidad computacional adicional en comparación con el ingenuo enfoque "rehash n times", mediante el uso de múltiples algoritmos de hashing, la generación de PRN con un estado interno bastante grande, std::seed_seq
siempre se alimenta de la salida hash completa y realiza condicionamiento adicional "de los valores, y por último, pero no menos importante, agregar algunas operaciones de punto flotante de precisión doble para unirlos todos, con suerte haciendo que todo el procedimiento sea hostil para GPU / ASIC.
El hash ahora toma aproximadamente 500 ms, comparado con los 5000 nsegs, en una CPU de 4 Ghz i7. Dado el total de 95 símbolos permitidos en una contraseña, y suponiendo una escala perfecta de hasta 8 hilos, a esta PC le tomará ~ 1450 años probar cada combinación posible para una contraseña de 6 caracteres, o ~ 130 años para una contraseña de 8 caracteres con 100k Tales CPUs, que parecen razonablemente exigentes.
¿Se considera esto "endurecido" lo suficiente y, de no ser así, cómo puedo mejorarlo?