Mi equipo recibió un código del lado del servidor (en Java) que genera tokens aleatorios y tengo una pregunta sobre lo mismo:
El propósito de estos tokens es bastante sensible: se usa para identificación de sesión, enlaces de restablecimiento de contraseña, etc. Por lo tanto, deben ser criptográficamente aleatorios para evitar que alguien los adivine o los fuerce de manera bruta. El token es "largo", por lo que tiene 64 bits de largo.
El código actualmente usa la java.util.Random
clase para generar estos tokens. La documentación para java.util.Random
establece claramente lo siguiente:
Las instancias de java.util.Random no son criptográficamente seguras. Considere en su lugar utilizar SecureRandom para obtener un generador de números pseudoaleatorios criptográficamente seguro para uso de aplicaciones sensibles a la seguridad.
Sin embargo, la forma en que el código está usando actualmente java.util.Random
es la siguiente: crea una instancia de la java.security.SecureRandom
clase y luego usa el SecureRandom.nextLong()
método para obtener la semilla que se usa para crear instancias de la java.util.Random
clase. Luego usa el java.util.Random.nextLong()
método para generar el token.
Entonces mi pregunta ahora: ¿sigue siendo inseguro dado que java.util.Random
se está sembrando java.security.SecureRandom
? ¿Necesito modificar el código para que se use java.security.SecureRandom
exclusivamente para generar los tokens?
Actualmente, la semilla del código es la Random
única en el inicio
fuente
Random
una vez al inicio, o genera una nueva para cada token? Con suerte, esta es una pregunta estúpida, pero pensé en comprobarlo.long
odouble
valores.Respuestas:
La implementación estándar de Oracle JDK 7 usa lo que se llama un Generador Congruencial Lineal para producir valores aleatorios
java.util.Random
.Tomado del
java.util.Random
código fuente (JDK 7u2), de un comentario sobre el métodoprotected int next(int bits)
, que es el que genera los valores aleatorios:Previsibilidad de generadores congruenciales lineales
Hugo Krawczyk escribió un artículo bastante bueno sobre cómo se pueden predecir estos LCG ("Cómo predecir generadores congruentes"). Si tiene suerte e interés, aún puede encontrar una versión gratuita y descargable en la web. Y hay mucha más investigación que muestra claramente que nunca debe usar un LCG para fines críticos de seguridad. Esto también significa que sus números aleatorios son predecibles en este momento, algo que no desea para ID de sesión y similares.
Cómo romper un generador congruencial lineal
La suposición de que un atacante tendría que esperar a que se repita el LCG después de un ciclo completo es incorrecta. Incluso con un ciclo óptimo (el módulo m en su relación de recurrencia) es muy fácil predecir valores futuros en mucho menos tiempo que un ciclo completo. Después de todo, es solo un montón de ecuaciones modulares que deben resolverse, lo que se vuelve fácil tan pronto como haya observado suficientes valores de salida del LCG.
La seguridad no mejora con una semilla "mejor". Simplemente no importa si siembras con un valor aleatorio generado por
SecureRandom
o incluso si produces el valor tirando un dado varias veces.Un atacante simplemente calculará la semilla a partir de los valores de salida observados. Esto lleva mucho menos tiempo que 2 ^ 48 en el caso de
java.util.Random
. Los incrédulos pueden probar este experimento , donde se muestra que puede predecirRandom
resultados futuros observando solo dos (!) Valores de salida en el tiempo aproximadamente 2 ^ 16. No toma ni un segundo en una computadora moderna para predecir la salida de sus números aleatorios en este momento.Conclusión
Reemplace su código actual. Uso
SecureRandom
exclusivo. Entonces, al menos, tendrá una pequeña garantía de que el resultado será difícil de predecir. Si desea las propiedades de un PRNG criptográficamente seguro (en su caso, eso es lo que desea), debe elegirSecureRandom
solo. Ser inteligente para cambiar la forma en que se suponía que debía usarse casi siempre resultará en algo menos seguro ...fuente
Random
esté roto, solo debe usarse en diferentes escenarios. Por supuesto, siempre puedes usar SecureRandom. Pero en general,SecureRandom
es notablemente más lento que puroRandom
. Y hay casos en los que solo le interesan las buenas propiedades estadísticas y el excelente rendimiento, pero realmente no le importa la seguridad: las simulaciones de Monte-Carlo son un buen ejemplo. Hice comentarios sobre eso en una respuesta similar , tal vez lo encuentres útil.Un aleatorio tiene solo 48 bits, mientras que SecureRandom puede tener hasta 128 bits. Por lo tanto, las posibilidades de repetir en securerandom son muy pequeñas.
Random usa el
system clock
como semilla / o para generar la semilla. Por lo tanto, pueden reproducirse fácilmente si el atacante sabe la hora en que se generó la semilla. Pero SecureRandom toma de su (pueden ser intervalos entre pulsaciones de teclas, etc., la mayoría de los recopilan estos datos y los almacenan en archivos ) y los utiliza como semilla. Entonces, si el tamaño del token pequeño está bien (en caso de Aleatorio), puede continuar usando su código sin ningún cambio, ya que está utilizando SecureRandom para generar la semilla. Pero si desea tokens más grandes (a los que no puede estar sujeto ), use SecureRandom: En caso de que se requieran solo intentos aleatorios , con las CPU avanzadas de hoy en día es posible romperlo en el tiempo práctico. Pero para que se requieran intentos aleatorios seguros , lo que llevará años y años alcanzar el equilibrio con las máquinas avanzadas de hoy. Vea este enlace para más detalles. EDITARRandom Data
os
/dev/random and /dev/urandom in case of linux/solaris
brute force attacks
2^48
2^128
Después de leer los enlaces proporcionados por @emboss, está claro que la semilla, por aleatoria que sea, no debería usarse con java.util.Random. Es muy fácil calcular la semilla observando la salida.
Vaya a SecureRandom : use PRNG nativo (como se indica en el enlace anterior) porque toma valores aleatorios del
/dev/random
archivo para cada llamada anextBytes()
. De esta forma, un atacante que observe la salida no podrá distinguir nada a menos que esté controlando el contenido del/dev/random
archivo (lo cual es muy poco probable).El algoritmo sha1 prng calcula la semilla solo una vez y si su VM está funcionando durante meses usando el mismo semilla, podría ser rajado por un atacante que observa pasivamente la salida
NOTA : Si está llamando
nextBytes()
más rápido de lo que su sistema operativo puede escribir bytes aleatorios (entropía) en el/dev/random
, puede tener problemas al usar NATIVE PRNG . En ese caso, use una instancia SHA1 PRNG de SecureRandom y cada pocos minutos (o algún intervalo), siembre esta instancia con el valor denextBytes()
de una instancia NATIVE PRNG de SecureRandom. Ejecutar estos dos paralelos asegurará que esté sembrando regularmente con valores aleatorios verdaderos, sin agotar la entropía obtenida por el sistema operativo.fuente
Random
, el OP no debería usarseRandom
en absoluto./proc/sys/kernel/random/entropy_avail
y verifique con algunos volcados de hilos que no haya demasiado tiempo de espera al seguir leyendo/dev/random
Si corre dos veces
java.util.Random.nextLong()
con la misma semilla, producirá el mismo número. Por razones de seguridad que desea seguirjava.security.SecureRandom
porque es mucho menos predecible.Las 2 clases son similares, creo que sólo tiene que cambiar
Random
aSecureRandom
una herramienta de refactorización y la mayor parte de su código existente funcionará.fuente
Si cambiar su código existente es una tarea asequible, le sugiero que use la clase SecureRandom como se sugiere en Javadoc.
Incluso si encuentra que la implementación de la clase Random usa la clase SecureRandom internamente. no debes dar por sentado que:
Por lo tanto, es una mejor opción seguir la sugerencia de documentación e ir directamente con SecureRandom.
fuente
java.util.Random
implementación se usaSecureRandom
internamente, dice que su código se usaSecureRandom
para sembrar elRandom
. Aún así, estoy de acuerdo con ambas respuestas hasta ahora; Es mejor usarSecureRandom
para evitar una solución explícitamente determinista.La implementación de referencia actual de
java.util.Random.nextLong()
hace dos llamadas al métodonext(int)
que expone directamente 32 bits de la semilla actual:Los 32 bits superiores del resultado de
nextLong()
son los bits de la semilla en ese momento. Como el ancho de la semilla es de 48 bits (dice el javadoc), es suficiente * iterar sobre los 16 bits restantes (eso es solo 65.536 intentos) para determinar la semilla que produjo el segundo 32 bits.Una vez que se conoce la semilla, todos los siguientes tokens se pueden calcular fácilmente.
Usando la salida de
nextLong()
directamente, en parte el secreto de PNG en un grado tal que todo el secreto se puede calcular con muy poco esfuerzo. ¡Peligroso!* Se necesita cierto esfuerzo si los segundos 32 bits son negativos, pero uno puede descubrirlo.
fuente
La semilla no tiene sentido. Un buen generador aleatorio difiere en el número primario elegido. Cada generador aleatorio comienza desde un número e itera a través de un 'anillo'. Lo que significa que vienes de un número al siguiente, con el antiguo valor interno. Pero después de un tiempo vuelves al principio y comienzas de nuevo. Entonces corres ciclos. (el valor de retorno de un generador aleatorio no es el valor interno)
Si usa un número primo para crear un anillo, se eligen todos los números en ese anillo, antes de completar un ciclo completo a través de todos los números posibles. Si toma números no primos, no se eligen todos los números y se obtienen ciclos más cortos.
Los números primos más altos significan ciclos más largos antes de volver al primer elemento nuevamente. Entonces, el generador aleatorio seguro solo tiene un ciclo más largo, antes de llegar al principio nuevamente, por eso es más seguro. No puede predecir la generación de números tan fácil como con ciclos más cortos.
Con otras palabras: tienes que reemplazar todo.
fuente
Intentaré usar palabras muy básicas para que puedas entender fácilmente la diferencia entre Random y secureRandom y la importancia de SecureRandom Class.
¿Alguna vez se preguntó cómo se genera la OTP (contraseña única)? Para generar una OTP, también usamos las clases Random y SecureRandom. Ahora para fortalecer su OTP, SecureRandom es mejor porque tomó 2 ^ 128 intentos, descifrar el OTP que es casi imposible con la máquina actual, pero si se usa Random Class, entonces su OTP puede ser descifrado por alguien que puede dañar sus datos porque se tomó solo 2 ^ 48 intento, para romper.
fuente