getinstancestrong - Diferencia entre java.util.Random y java.security.SecureRandom
securerandom ruby (6)
La implementación de referencia actual de java.util.Random.nextLong()
realiza dos llamadas al método next(int)
que expone directamente 32 bit 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. Como el ancho de la semilla es de 48 bits (dice el javadoc), basta * iterar sobre los 16 bits restantes (eso es solo 65.536 intentos) para determinar la semilla que produjo los segundos 32 bits.
Una vez que se conoce la semilla, todos los siguientes tokens pueden ser fácilmente calculados.
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 algún esfuerzo si los segundos 32 bits son negativos, pero uno puede descubrirlo.
Mi equipo recibió un código del lado del servidor (en Java) que genera tokens aleatorios y tengo una pregunta sobre el 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 forzarlos de forma brutal. La ficha es "larga" por lo que tiene 64 bits de longitud.
El código actualmente usa la clase java.util.Random
para generar estos tokens. La documentación ([ http://docs.oracle.com/javase/7/docs/api/java/util/Random.html][1] ) para java.util.Random
establece claramente lo siguiente:
Las instancias de java.util.Random no son criptográficamente seguras. Considere usar SecureRandom para obtener un generador de números pseudoaleatorios criptográficamente seguro para aplicaciones sensibles a la seguridad.
Sin embargo, la forma en que el código está usando actualmente java.util.Random
es esto: java.security.SecureRandom
instancia de la clase java.security.SecureRandom
y luego utiliza el método SecureRandom.nextLong()
para obtener la semilla que se utiliza para crear instancias de java.util.Random
Clase java.util.Random
Luego usa el método java.util.Random.nextLong()
para generar el token.
Entonces mi pregunta ahora: ¿sigue siendo insegura dado que java.util.Random
está siendo sembrado usando java.security.SecureRandom
? ¿Debo modificar el código para que use java.security.SecureRandom
exclusivamente para generar los tokens?
Actualmente el código semilla es el Random
una vez al inicio
La implementación estándar de Oracle JDK 7 utiliza lo que se denomina Generador lineal congruente 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 congruente lineal, como lo define DH Lehmer y descrito por Donald E. Knuth en The Art of Computer Programming, Volumen 3: Algoritmos Seminumerical , sección 3.2.1.
Previsibilidad de generadores congruenciales lineales
Hugo Krawczyk escribió un documento bastante bueno sobre cómo se pueden predecir estos LCG ("Cómo predecir generadores congruenciales"). Si tiene suerte e interés, puede encontrar una versión descargable gratuita en la web. Y hay mucha más investigación que muestra claramente que nunca se debe usar un LCG para fines de seguridad crítica. Esto también significa que sus números aleatorios son predecibles en este momento, algo que no desea para las 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 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 solo un montón de ecuaciones modulares que deben ser resueltas, 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 SecureRandom
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 experiment , donde se muestra que puedes predecir futuras salidas Random
observando solo dos (!) Valores de salida en el tiempo aproximadamente 2 ^ 16. No lleva ni un segundo en una computadora moderna predecir la salida de sus números aleatorios en este momento.
Conclusión
Reemplace su código actual. Use SecureRandom
exclusivamente. Entonces, al menos, tendrás un poco de garantía de que el resultado será difícil de predecir. Si quiere las propiedades de un PRNG criptográficamente seguro (en su caso, eso es lo que quiere), entonces tiene que ir solo con SecureRandom
. Ser inteligente al cambiar la forma en que se suponía que debía usarse casi siempre resultaría en algo menos seguro ...
La semilla no tiene sentido Un buen generador aleatorio difiere en el primenúmero 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, llegas al principio otra vez y comienzas todo 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 obtiene ciclos más cortos.
Números primos más altos significan ciclos más largos antes de regresar al primer elemento nuevamente. Por lo tanto, el generador aleatorio seguro solo tiene un ciclo más largo, antes de volver a empezar, por eso es más seguro. No puede predecir la generación de números tan fácil como con ciclos más cortos.
En otras palabras: tienes que reemplazar todo.
Si cambiar su código actual 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:
- Otras implementaciones de VM hacen lo mismo.
- La implementación de la clase Random en versiones futuras de JDK aún usa la clase SecureRandom
Por lo tanto, es una mejor opción seguir la sugerencia de documentación e ir directamente con SecureRandom.
Si ejecuta dos veces java.util.Random.nextLong()
con la misma semilla, producirá el mismo número. Por razones de seguridad, quiere seguir con java.security.SecureRandom
porque es mucho menos predecible.
Las 2 clases son similares, creo que solo necesita cambiar Random
por SecureRandom
con una herramienta de refactorización y la mayoría de su código existente funcionará.
Un azar tiene solo 48 bits donde SecureRandom puede tener hasta 128 bits. Entonces las posibilidades de repetir en securerandom son muy pequeñas.
Random usa el system clock
del 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 Random Data
de su sistema os
(pueden ser intervalos entre pulsaciones de teclas, etc., la mayoría de los sistemas OS recopilan estos datos, los almacenan en archivos /dev/random and /dev/urandom in case of linux/solaris
) y los utilizan como semilla.
Por lo tanto, si el tamaño del token pequeño es correcto (en el caso de Aleatorio), puede continuar usando su código sin ningún cambio, ya que está utilizando SecureRandom para generar la semilla. Pero si quieres tokens más grandes (que no pueden estar sujetos a brute force attacks
) ve con SecureRandom -
En caso de que sea aleatorio, solo se requieren 2^48
intentos, con la CPU avanzada de hoy en día es posible romperla en el tiempo práctico. Pero para securerandom se necesitarán 2^128
intentos, lo que llevará años y años para llegar al punto de equilibrio con las máquinas avanzadas de la actualidad.
Vea this enlace para más detalles.
EDITAR
Después de leer los enlaces provistos por @emboss, está claro que la semilla, por aleatoria que sea, no debe usarse con java.util.Random. Es muy fácil calcular la semilla al observar la salida.
Vaya a SecureRandom : use Native PRNG (como se indica en el enlace anterior) porque toma valores /dev/random
archivo /dev/random
para cada llamada a nextBytes()
. De esta forma, un atacante que observe la salida no podrá distinguir nada a menos que esté controlando el contenido del archivo /dev/random
(que es muy poco probable)
El algoritmo sha1 prng calcula la semilla solo una vez y si su máquina virtual se está ejecutando durante meses usando la misma semilla, podría ser descifrada por un atacante que esté observando pasivamente la salida.
NOTA - Si está llamando al nextBytes()
más rápido que su sistema operativo es capaz de escribir bytes aleatorios (entropía) en /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), nextBytes()
esta instancia con el valor de nextBytes()
de una instancia NAANT PRNG de SecureRandom. Si ejecuta estos dos pasos paralelos, se asegurará de que esté realizando la siembra regularmente con valores aleatorios verdaderos, sin agotar la entropía obtenida por el sistema operativo.