BCrypt 性能下降

BCrypt performance deterioration

我们在 Jboss 服务器 6.1 中有三个 Web 应用程序(标准 Spring MVC-Hibernate)运行。所有三个应用程序共享一个共同的身份验证方法,该方法被编译为 JAR 并包含在每个 WAR 文件中。我们的身份验证方法使用 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 秒。这没有明显的原因。

更多信息:

有没有其他人以前遇到过这个问题?

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

Springs BCrypt 实现 uses SecureRandom which in turn uses a SeedGenerator which in turn may use the blocking /dev/random. Here 很好地描述了那些 类。

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

Problem: It appears that when the server is restarted, the Bcrypt.hashpw takes 100ms to decrypt password. However after some time (there is no pattern) suddenly the Bcrypt.hashpw performance spikes up from 100ms to 10s of seconds. There is no obvious reason for this.

问题是 /dev/random 有时会阻塞,当它阻塞时,它会显示为 运行dom :) 更令人困惑的是,在尝试测试它如何工作时,你会 运行 反对观察者效应,即在尝试观察 运行dom 行为时你正在产生熵,这可能会导致大量混乱,即我的结果将与你的结果不同等。这也是为什么看起来没有图案..

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

在 Linux /dev/random 上是一个 运行dom 字节流。当你从 流你耗尽了可用的熵。当它到达某个点时 从 /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 运行ning 它会产生影响,它会消耗 运行dom 字节流,这意味着其他服务可能会开始阻塞。请注意,pv 也在显示它的输出,因此它也可能会增加系统上的可用熵:)。

在熵很少或没有熵的系统上使用 pv /dev/random 会显得非常慢。我还遇到过 VM 有时在生成熵方面存在重大问题。

要重现问题,请使用以下 class...

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

如果您然后 运行 之前的 bash 脚本,而 运行nng RandTest 您会看到系统阻塞等待更多熵的大量停顿。如果你 运行 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, "047RB0", 8) = 6

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

修复

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

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

要记住的关键是测试 运行dom 数据需要从 由于观察者效应,您正在测试的系统很困难。

更改为 urandom 标签仅适用于 JDK8 或更高版本,我们长期以来一直面临这个问题,在 1.7 中更改为 urandom 没有帮助,但在 1.8 中它确实解决了问题。

对于遇到同样问题的任何人,我们通过安装 rng-tools

解决了这个问题