BCrypt性能恶化

我们在Jboss服务器6.1中运行了三个Web应用程序(标准的Spring MVC-Hibernate)。 所有这三个应用程序共享一个通用的身份validation方法,该方法被编译为JAR并包含在每个WAR文件中。 我们的身份validation方法使用org.springframework.security.crypto.bcrypt.BCrypt来哈希用户密码,请参阅以下内容:

hashedPassword.equals(BCrypt.hashpw(plainTextPassword, salt)); 

JBOSS启动选项

set "JAVA_OPTS=-Xms2048m -Xmx4096m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -verbosegc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.txt -XX:+UseParallelOldGC

问题:当服务器重新启动时,Bcrypt.hashpw需要100ms来解密密码。 然而,经过一段时间(没有模式),Bcrypt.hashpw性能突然从100毫秒增加到10秒。 这没有明显的原因。

更多信息:

  • Hibernate版本:4.2.4.Final
  • Spring Version:4.0.5.RELEASE Spring
  • 安全版本:3.2.4.RELEASE

有没有其他人见过这个问题?

问题:当服务器重新启动时,Bcrypt.hashpw需要100ms来解密密码。 然而,经过一段时间(没有模式),Bcrypt.hashpw性能突然从100毫秒增加到10秒。 这没有明显的原因。

问题是/dev/random有时会阻塞,当它出现时它似乎是随机的:)更令人困惑的是,在尝试测试它是如何工作的时候,你会遇到观察者效应,即在尝试观察随机行为时你正在产生熵,这可能导致大量的混乱,即我的结果将与你的结果不一样等。这也是为什么它看起来没有模式..

我将演示该问题,并向您展示如何在您自己的服务器上重新创建它(以便在合理范围内),以便您可以测试解决方案。 我将尝试提供一些修复,请注意这是在Linux上,但同样的问题将发生在需要熵生成随机数并耗尽的任何系统上。

在Linux /dev/random是随机字节流。 当你从流中读取时,你耗尽了可用的熵。 当它到达某一点时从/dev/random块读取。 您可以使用此命令查看可用的熵

 cat /proc/sys/kernel/random/entropy_avail 

如果您运行以下bash脚本并监视entropy_avail您会发现当bash脚本使用它时,熵会急剧下降。

 while : do cat /dev/random > /dev/null done 

这也应该为您提供如何在服务器上重新创建此问题的提示,即运行上面的bash脚本以减少可用的熵,问题就会显现出来。

如果您想查看系统每秒创建的字节数,您可以使用pv来测量它,即

 pv /dev/random 

如果你让pv运行它会产生影响,它会消耗随机的字节流,这意味着其他服务可能会开始阻塞。 请注意, pv也显示它的输出,因此它也可能在系统上增加可用的修改:)。

在使用pv /dev/random很少或没有熵的系统上看起来似乎很慢。 我也经历过VM有时会产生熵的主要问题。

要重新创建该问题,请使用以下类…

 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); } } } 

我将bcrypt下载到本地目录。 我编译并运行如下

 javac -cp ./jBCrypt-0.4/src/ RandTest.java java -cp ./jBCrypt-0.4/src/:. RandTest 

如果您在运行RandTest时运行之前的bash脚本,您将看到系统阻塞等待更多熵的大停顿。 如果你运行strace你会看到以下内容......

 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 

该程序正在读取/dev/random 。 测试熵的问题在于,在尝试测试熵时可能会产生更多,即观察者效应。

修复

第一个修复是从使用/dev/random更改为/dev/urandom ie

 time java -Djava.security.egd=file:///dev/./urandom -cp ./jBCrypt-0.4/src/:. RandTest 

另一种解决方法是将/dev/random设备重新创建为/dev/urandom设备。 您可以在手册页中找到如何执行此操作,而不是创建它们...

 mknod -m 644 /dev/random c 1 8 mknod -m 644 /dev/urandom c 1 9 chown root:root /dev/random /dev/urandom 

我们删除一个并伪造它即

 rm /dev/random mknod -m 644 /dev/random c 1 9 chown root:root /dev/random 

/dev/random现在实际上是/dev/urandom

要记住的关键是要测试随机数据,这些数据需要从您正在测试的系统中输入,因为观察者效应很困难。

一种可能的解释是SecureRandomSeedGenerator导致延迟。

Springs BCrypt实现使用 SecureRandom ,而SecureRandom又使用SeedGenerator ,而SeedGenerator可以使用阻塞/dev/random 。 这是对这些课程的一个很好的描述。

该bug报告还报告了BCrypt中的性能问题,并将它们追溯到种子生成器,显示完整的堆栈跟踪。 BCrypt实现不同,但SecureRandom下面的堆栈跟踪必须与spring实现相同。 他们的解决方案是减少BCrypt的重新种植频率。