Diferencia entre java.útil.Random y java.seguridad.SecureRandom


Mi equipo recibió un código del lado del servidor (en Java) que genera tokens aleatorios y tengo una pregunta con respecto al mismo -

El propósito de estos tokens es bastante sensible: se usa para id de sesión, enlaces de restablecimiento de contraseña, etc. Así que necesitan ser criptográficamente aleatorios para evitar que alguien los adivine o los obligue de manera factible. El token es un "largo" por lo que es de 64 bits de largo.

El código utiliza actualmente la clase java.util.Random para generar estos tokens. Documentación ([http://docs.oracle.com/javase/7/docs/api/java/util/Random.html][1]) para java.util.Random establece claramente lo siguiente:

Instancias de java.útil.El azar no es criptográficamente seguro. Considere usar 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 esta: Crea una instancia de la clase java.security.SecureRandom y luego usa el método SecureRandom.nextLong() para obtener la semilla que se utiliza para instanciar la clase java.util.Random. Luego utiliza el método java.util.Random.nextLong() para generar el token.

Así que mi pregunta ahora - Es todavía inseguro dado que el java.util.Random se está sembrando usando java.security.SecureRandom? ¿Necesito modificar el código para que utilice java.security.SecureRandom exclusivamente para generar los tokens?

Actualmente el código seed es el Random una vez al inicio

Author: Robert Harvey, 2012-06-15

7 answers

La implementación estándar de Oracle JDK 7 utiliza lo que se llama un Generador Congruente Lineal para producir valores aleatorios en java.util.Random.

Tomado del código fuente java.util.Random (JDK 7u2), de un comentario sobre el método protected int next(int bits), que es el que genera los valores aleatorios:

Este es un generador de números pseudoaleatorios congruentes lineales, como definido por D. H. Lehmer y descrito por Donald E. Knuth en El arte de la Programación Informática, Volumen 3: Algoritmos seminuméricos , sección 3.2.1.

Previsibilidad de los Generadores Congruenciales Lineales

Hugo Krawczyk escribió un artículo bastante bueno sobre cómo se pueden predecir estos LCG ("How to predict congruential generators"). Si tienes suerte e interés, todavía puede encontrar una versión gratuita y descargable de la misma en la web. Y hay mucha más investigación que muestra claramente que debe nunca 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 los ID de sesión y similares.

Cómo romper un Generador Congruente Lineal

La suposición de que un atacante tendría que esperar a que el LCG se repita 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 sólo un montón de ecuaciones modulares que necesitan a resolver, que se vuelve fácil tan pronto como se han observado suficientes valores de salida de la LCG.

La seguridad no mejora con una semilla "mejor". Simplemente no importa si se siembra con un valor aleatorio generado por SecureRandom o incluso produce el valor rodando un dado varias veces.

Un atacante simplemente calculará la semilla a partir de los valores de salida observados. Esto toma significativamente menos tiempo que 2^48 en el caso de java.util.Random. Los incrédulos pueden probar esto experimento, donde se muestra que se puede predecir el futuro Random salidas observando solo dos(!) valores de salida en el tiempo aproximadamente 2^16. No se necesita ni un segundo en una computadora moderna para predecir la salida de sus números aleatorios en este momento.

Conclusión

Reemplaza tu código actual. Utilizar SecureRandom exclusivamente. 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 quieres), entonces tienes que ir con SecureRandom solamente. Ser inteligente al cambiar la forma en que se suponía que debía usarse casi siempre resultará en algo menos seguro...

 217
Author: emboss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-02 00:38:14

Un random tiene solo 48 bits donde as SecureRandom puede tener hasta 128 bits. Por lo tanto, las posibilidades de repetir en securerandom son muy pequeñas.

Aleatorio utiliza el system clock como la semilla/o para generar la semilla. Por lo tanto, pueden reproducirse fácilmente si el atacante conoce el momento en el que se generó la semilla. Pero SecureRandom toma Random Data de su os (pueden ser intervalos entre pulsaciones de teclas, etc.-la mayoría de los sistemas operativos recopilan estos datos y los almacenan en archivos - /dev/random and /dev/urandom in case of linux/solaris) y los usa como semilla.
Así que si el tamaño pequeño del token 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 (que no pueden estar sujetos a brute force attacks) ir con SecureRandom -
En caso de azar solo 2^48 se requieren intentos, con cpu avanzadas de hoy es posible romper en tiempo práctico. Pero para securerandom 2^128 se requerirán intentos, que tomarán años y años para romper el equilibrio con las máquinas avanzadas de hoy.

Vea este enlace para más detalles.
EDITAR
Después de leer los enlaces proporcionados por @emboss, está claro que la semilla, por aleatoria que sea tal vez, no se debe usar con java.útil.Aleatorio. 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 archivo /dev/random para cada llamada a nextBytes(). Este forma en que un atacante que observa la salida no podrá distinguir nada a menos que esté controlando el contenido del archivo /dev/random (lo cual es muy poco probable)
El algoritmo sha1 prng calcula la semilla solo una vez y si su VM se está ejecutando durante meses usando la misma semilla, podría ser descifrada por un atacante que esté observando pasivamente la salida.

NOTA - Si usted está llamando a la nextBytes() más rápido que su sistema operativo es capaz de escribir bytes aleatorios (entropía) en la /dev/random, usted podría tener problemas al usar PRNG NATIVO. En ese caso use una instancia PRNG SHA1 de SecureRandom y cada pocos minutos(o algún intervalo), seed esta instancia con el valor de nextBytes() de una instancia PRNG NATIVA de SecureRandom. Ejecutar estos dos en paralelo asegurará que se siembra regularmente con valores aleatorios verdaderos, al tiempo que no agota la entropía obtenida por el Sistema Operativo.

 64
Author: Ashwin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-12 13:12:08

Si se ejecuta dos veces java.util.Random.nextLong() con la misma semilla, producirá el mismo número. Por razones de seguridad que desea seguir con java.security.SecureRandom porque es mucho menos predecible.

Las 2 Clases son similares, creo que solo necesita cambiar Random a SecureRandom con una herramienta de refactorización y la mayor parte de su código existente funcionará.

 8
Author: Mualig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-15 13:10:37

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 aleatoria usa la clase SecureRandom internamente. no debe dar por sentado que:

  1. Otras implementaciones de VM hacen lo mismo.
  2. La implementación de la clase Random en futuras versiones del JDK todavía usa la clase SecureRandom

Así que es una mejor opción seguir el sugerencia de documentación e ir directamente con SecureRandom.

 3
Author: Andrea Parodi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-21 04:19:15

La semilla no tiene sentido. Un buen generador aleatorio difiere en el primenumber elegido. Cada generador aleatorio comienza a partir de un número e itera a través de un 'anillo'. Lo que significa que vienes de un número al siguiente, con el viejo valor interno. Pero después de un tiempo llegas al principio de nuevo y empiezas todo de nuevo. Así que corres ciclos. (el valor devuelto por un generador aleatorio no es el valor interno)

Si usa un número primo para crear un anillo, todos los números en ese anillo obtienen elegido, antes de completar un ciclo completo a través de todos los números posibles. Si tomas números no primos, no todos los números son elegidos y obtienes ciclos más cortos.

Los números primos más altos significan ciclos más largos, antes de volver al primer elemento de nuevo. Por lo tanto, el generador aleatorio seguro solo tiene un ciclo más largo, antes de llegar al principio de nuevo, es por eso que es más seguro. No se puede predecir la generación de números tan fácil como con ciclos más cortos.

Con otras palabras: Tienes que reemplazar todo.

 2
Author: Nicolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-24 22:22:59

La implementación de referencia actual de java.util.Random.nextLong() hace dos llamadas al método next(int) que directamente expone 32 bits de la semilla actual:

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

Los 32 bits superiores del resultado de nextLong() son los bits de la semilla en el momento. Dado que el ancho de la semilla es de 48 bits (dice el javadoc), basta* con 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 pueden fácil de calcular.

Usando la salida de nextLong() directamente, en parte el secreto del PNG hasta el punto de que todo el secreto se puede calcular con muy poco esfuerzo. Peligroso!

* Se necesita algún esfuerzo si el segundo bit de 32 es negativo, pero uno puede averiguarlo.

 1
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-21 14:49:49

Trataré de usar palabras muy básicas para que puedas entender fácilmente la diferencia entre Random y SecureRandom y la importancia de la Clase SecureRandom.

¿Alguna vez te has preguntado cómo se genera OTP(one time password)? Para generar una OTP también usamos la clase Random y SecureRandom. Ahora para hacer su OTP fuerte, SecureRandom es mejor porque tomó 2^128 intento, para romper el OTP que es casi imposible por la máquina actual, pero si se utiliza Clase aleatoria entonces su OTP puede ser agrietado por alguien que puede dañar sus datos, ya que tomó solo 2^48 tratar, para agrietarse.

 0
Author: sachin pathak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-11 11:06:41