java - online - Deterioro del rendimiento de BCrypt
desencriptar bcrypt java (2)
Tenemos tres aplicaciones web (estándar Spring MVC-Hibernate) que se ejecutan dentro de un servidor Jboss 6.1. Las tres aplicaciones comparten un método de autenticación común que se compila como un JAR y se incluye dentro de cada archivo WAR. Nuestro método de autenticación utiliza org.springframework.security.crypto.bcrypt.BCrypt para hacer hash de las contraseñas de los usuarios, consulte a continuación:
hashedPassword.equals(BCrypt.hashpw(plainTextPassword, salt));
Opciones de inicio de JBOSS
set "JAVA_OPTS=-Xms2048m -Xmx4096m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -verbosegc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.txt -XX:+UseParallelOldGC
Problema: parece que cuando se reinicia el servidor, Bcrypt.hashpw tarda 100 ms en descifrar la contraseña. Sin embargo, después de algún tiempo (no hay patrón), de repente, el rendimiento de Bcrypt.hashpw aumenta de 100 ms a 10 segundos. No hay ninguna razón obvia para esto.
Más información:
- Versión Hibernate: 4.2.4.Final
- Versión de primavera: 4.0.5.RELEASE Primavera
- Versión de seguridad: 3.2.4.RELEASE
¿Alguien más ha visto este problema antes?
Problema: parece que cuando se reinicia el servidor, Bcrypt.hashpw tarda 100 ms en descifrar la contraseña. Sin embargo, después de algún tiempo (no hay patrón), de repente, el rendimiento de Bcrypt.hashpw aumenta de 100 ms a 10 segundos. No hay ninguna razón obvia para esto.
El problema es que /dev/random
veces bloquea y cuando lo hace parecerá ser aleatorio :) Lo más confuso es que al intentar probar cómo funciona, se enfrentará al Efecto Observador, es decir, al intentar observar un comportamiento aleatorio. estás generando entropía y esto puede llevar a un montón de confusión, es decir, mis resultados no serán los mismos que los tuyos, etc. También es por esto que parece que no hay un patrón ...
Le demostraré el problema y le mostraré cómo recrearlo (dentro de lo razonable) en sus propios servidores para que pueda probar las soluciones. Intentaré proporcionar un par de soluciones, tenga en cuenta que esto está en Linux, pero el mismo problema ocurrirá en cualquier sistema que requiera entropía para generar números aleatorios y se agote.
En Linux /dev/random
hay un flujo de bytes aleatorios. A medida que lees la secuencia, agotas la entropía disponible. Cuando alcanza un cierto punto lee desde /dev/random
block. Puedes ver la entropía disponible usando este comando
cat /proc/sys/kernel/random/entropy_avail
Si ejecuta la siguiente secuencia de comandos bash y también supervisa entropy_avail
, notará que la entropía se reduce drásticamente a medida que la secuencia de comandos bash la consume.
while :
do
cat /dev/random > /dev/null
done
Esto también debería darle una pista sobre cómo volver a crear este problema en sus servidores, es decir, ejecutar el script de bash anterior para reducir la entropía disponible y el problema se manifestará.
Si desea ver cuántos bytes por segundo está creando su sistema, puede usar pv
para medirlo, es decir,
pv /dev/random
Si deja que pv
ejecute, tiene un efecto, está consumiendo el flujo aleatorio de bytes, lo que significa que otros servicios podrían comenzar a bloquearse. Tenga en cuenta que pv
también está mostrando su salida, por lo que también podría estar aumentando la función de entrada disponible en el sistema :).
En sistemas con poca o ninguna entropía, pv /dev/random
parecerá lento en el glaciar. También he experimentado que las máquinas virtuales a veces tienen problemas importantes con la generación de entropía.
Para recrear el problema usa la siguiente clase ...
import java.security.SecureRandom;
import org.mindrot.jbcrypt.BCrypt;
public class RandTest {
public static void main(String[] args) {
SecureRandom sr = new SecureRandom();
int out = 0;
String password = "very-strong-password-1729";
String hashed;
for (int i = 0; i < 200000 ; i++) {
hashed = BCrypt.hashpw(password, BCrypt.gensalt());
//If we print, we''re generating entroy :) System.out.println(hashed);
}
}
}
Descargué bcrypt a un directorio local. Lo compilé y lo ejecuté como sigue
javac -cp ./jBCrypt-0.4/src/ RandTest.java
java -cp ./jBCrypt-0.4/src/:. RandTest
Si luego ejecuta la secuencia de comandos bash anterior mientras ejecuta RandTest
, verá grandes pausas donde el sistema está bloqueando esperando más entropía. Si ejecutas strace
verás lo siguiente ...
1067 [pid 22481] open("/dev/random", O_RDONLY|O_LARGEFILE) = 12
11068 [pid 22481] fstat64(12, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
11069 [pid 22481] fcntl64(12, F_GETFD) = 0
11070 [pid 22481] fcntl64(12, F_SETFD, FD_CLOEXEC) = 0
.....
11510 [pid 22481] read(12, "/320/244/317RB/370", 8) = 6
El programa está leyendo desde /dev/random
. El problema con la prueba de entropía es que puede estar generando más mientras intenta probarla, es decir, el efecto de observador.
Arreglos
La primera solución es cambiar de usar /dev/random
a /dev/urandom
es decir
time java -Djava.security.egd=file:///dev/./urandom -cp ./jBCrypt-0.4/src/:. RandTest
Una solución alternativa es recrear el dispositivo /dev/random
como un dispositivo /dev/urandom
. Puede encontrar cómo hacer esto desde la página de manual, es decir, en lugar de crearlas ...
mknod -m 644 /dev/random c 1 8
mknod -m 644 /dev/urandom c 1 9
chown root:root /dev/random /dev/urandom
eliminamos uno y lo falsificamos, es decir
rm /dev/random
mknod -m 644 /dev/random c 1 9
chown root:root /dev/random
/dev/random
ahora es en realidad /dev/urandom
La clave a recordar es que la prueba de datos aleatorios que requiere el ingreso desde el sistema que está probando es difícil debido al efecto de observador.
Una posible explicación es que el SeedGenerator
de SecureRandom
está causando los retrasos.
La implementación de Springs BCrypt uses SecureRandom
que a su vez usa un SeedGenerator
que a su vez puede usar el bloqueo /dev/random
. Here hay una buena descripción de esas clases.
Ese informe de errores también informa sobre los problemas de rendimiento en BCrypt y los rastrea al generador de semillas, mostrando los stacktraces completos. La implementación de BCrypt es diferente, pero el seguimiento de pila por debajo de SecureRandom
debe ser idéntico a la implementación de Spring. Su solución fue reducir la frecuencia de resiembra de BCrypt.