使用 java 摘要生成哈希的时间变慢
Hash generation using java digest gets slower in time
在使用 wildfly 8.2 和 Java 1.7 的生产环境中,我遇到了一个非常奇怪的情况。
情况是,当服务器运行超过 2 周后,登录性能开始下降。我一直在寻找可能表明问题所在的线索。然后在做了一些测试之后,我得出的结论是,问题出在以明文形式插入的密码被加密以与已经插入的密码进行比较时。
当执行加密密码的函数时,它需要将近 2 分钟,但当服务器重新启动时,相同的执行时间不到 30 秒。
加密使用 java.security.MessageDigest 生成哈希。具体使用具有 50000 次迭代的 SHA-256。知道为什么这个过程会随着时间变慢吗?我正在使用 /dev/urandom 来生成随机数,所以这应该不是问题。
函数代码如下:
protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
MessageDigest digest = getDigest(getAlgorithmName());
if (salt != null) {
digest.reset();
digest.update(salt);
}
byte[] hashed = digest.digest(bytes);
int iterations = hashIterations - 1; //already hashed once above
//iterate remaining number:
for (int i = 0; i < iterations; i++) {
digest.reset();
hashed = digest.digest(hashed);
}
return hashed;
}
删除语句:int iterations = hashIterations - 1;
,只使用 hashIterations
。
在最好的情况下,它将迭代次数从 50000(在所述情况下)减少到 49999,在最坏的情况下会导致整数下溢并将迭代次数增加到最大值 int
。
当 hashIterations
为零时,至少要防止减去 1
。
还考虑通过记录 iterations
的值进行调试的检测。
为什么有人要关闭这个???可能是因为那里没有什么可能导致问题的原因。
虽然 digest.digest
通常是耗时的部分,但它是一个纯粹的计算,没有什么可以减慢它的速度。所以剩下的就是getAlgorithmName()
和getDigest(String)
。前者可能是一个微不足道的 getter,但后者可能使用定位摘要的 MessageDigest.getInstance
。只是猜测:对所有安全提供商及其提供的所有内容进行了查找,有人可能会以某种方式延长此列表。
即使在生产环境中,您也可以对该库方法进行基准测试:只需将方法复制到新的源文件中,并添加一些日志记录和一些定期调用它的代码(或手动,如果你比较喜欢)。当减速发生时,您将有一些东西可以比较,并且您会在日志中找到一些详细的时间安排。
当所有可以想象的原因都用尽时,尝试不可想象的原因,例如变化 iterations
(您认为它是常数)等
经过几天的研究,我终于找到了问题的答案。我想在这里分享它以防它对其他人有用。
问题是由代码缓存引起的。我专注于堆内存,没有发现任何问题,但是当我检查非堆内存时,我发现就在登录过程开始变慢时,代码缓存下降了一半以上已用内存。
调查这个内存,我发现当这个space有大的落差时,可能会出现JIT编译器停止工作的情况。总之,这就是正在发生的事情,关闭 JIT 编译器导致我的加密循环的每次迭代都必须在每次执行时进行解释,这在逻辑上使过程变慢了。
这里我留下了一些我认为对本主题有帮助的链接。
[2] - https://www.atlassian.com/blog/archives/codecache-is-full-compiler-has-been-disabled
感谢那些花时间回答的人
在使用 wildfly 8.2 和 Java 1.7 的生产环境中,我遇到了一个非常奇怪的情况。
情况是,当服务器运行超过 2 周后,登录性能开始下降。我一直在寻找可能表明问题所在的线索。然后在做了一些测试之后,我得出的结论是,问题出在以明文形式插入的密码被加密以与已经插入的密码进行比较时。
当执行加密密码的函数时,它需要将近 2 分钟,但当服务器重新启动时,相同的执行时间不到 30 秒。
加密使用 java.security.MessageDigest 生成哈希。具体使用具有 50000 次迭代的 SHA-256。知道为什么这个过程会随着时间变慢吗?我正在使用 /dev/urandom 来生成随机数,所以这应该不是问题。
函数代码如下:
protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
MessageDigest digest = getDigest(getAlgorithmName());
if (salt != null) {
digest.reset();
digest.update(salt);
}
byte[] hashed = digest.digest(bytes);
int iterations = hashIterations - 1; //already hashed once above
//iterate remaining number:
for (int i = 0; i < iterations; i++) {
digest.reset();
hashed = digest.digest(hashed);
}
return hashed;
}
删除语句:int iterations = hashIterations - 1;
,只使用 hashIterations
。
在最好的情况下,它将迭代次数从 50000(在所述情况下)减少到 49999,在最坏的情况下会导致整数下溢并将迭代次数增加到最大值 int
。
当 hashIterations
为零时,至少要防止减去 1
。
还考虑通过记录 iterations
的值进行调试的检测。
为什么有人要关闭这个???可能是因为那里没有什么可能导致问题的原因。
虽然 digest.digest
通常是耗时的部分,但它是一个纯粹的计算,没有什么可以减慢它的速度。所以剩下的就是getAlgorithmName()
和getDigest(String)
。前者可能是一个微不足道的 getter,但后者可能使用定位摘要的 MessageDigest.getInstance
。只是猜测:对所有安全提供商及其提供的所有内容进行了查找,有人可能会以某种方式延长此列表。
即使在生产环境中,您也可以对该库方法进行基准测试:只需将方法复制到新的源文件中,并添加一些日志记录和一些定期调用它的代码(或手动,如果你比较喜欢)。当减速发生时,您将有一些东西可以比较,并且您会在日志中找到一些详细的时间安排。
当所有可以想象的原因都用尽时,尝试不可想象的原因,例如变化 iterations
(您认为它是常数)等
经过几天的研究,我终于找到了问题的答案。我想在这里分享它以防它对其他人有用。
问题是由代码缓存引起的。我专注于堆内存,没有发现任何问题,但是当我检查非堆内存时,我发现就在登录过程开始变慢时,代码缓存下降了一半以上已用内存。
调查这个内存,我发现当这个space有大的落差时,可能会出现JIT编译器停止工作的情况。总之,这就是正在发生的事情,关闭 JIT 编译器导致我的加密循环的每次迭代都必须在每次执行时进行解释,这在逻辑上使过程变慢了。
这里我留下了一些我认为对本主题有帮助的链接。
[2] - https://www.atlassian.com/blog/archives/codecache-is-full-compiler-has-been-disabled
感谢那些花时间回答的人