(注意:另请参阅本答案末尾的最新更新)
说明
原因是默认
SecureRandom提供程序。
在Windows上,有2个
SecureRandom提供程序可用:
- provider=SUN, type=SecureRandom, algorithm=SHA1PRNG- provider=SunMSCAPI, type=SecureRandom, algorithm=Windows-PRNG
在Linux上(在Alpine docker中使用Oracle JDK 8u162进行了测试):
- provider=SUN, type=SecureRandom, algorithm=NativePRNG- provider=SUN, type=SecureRandom, algorithm=SHA1PRNG- provider=SUN, type=SecureRandom, algorithm=NativePRNGBlocking- provider=SUN, type=SecureRandom, algorithm=NativePRNGNonBlocking
这些在
jre/lib/security/java.security文件中指定。
security.provider.1=sun.security.provider.Sun...security.provider.10=sun.security.mscapi.SunMSCAPI
默认情况下,使用第一个
SecureRandom提供程序。在Windows上,默认值是
sun.security.provider.Sun,当使用JVM运行JVM时,此实现报告如下
-Djava.security.debug="provider,engine=SecureRandom":
Provider: SecureRandom.SHA1PRNG algorithm from: SUNprovider: Failed to use operating system seed generator: java.io.IOException: Required native CryptoAPI features not available on this machineprovider: Using default threaded seed generator
而且默认的线程种子生成器非常慢。
您需要使用
SunMSCAPI提供程序。
解决方案1:配置
重新配置供应商:
编辑
jre/lib/security/java.security:
security.provider.1=sun.security.mscapi.SunMSCAPI...security.provider.10=sun.security.provider.Sun
我不知道这可以通过系统属性来完成。
也许是的,使用
-Djava.security.properties(未经测试,请参阅此)
解决方案2:程序化
以编程方式重新排序提供者:
Optional.ofNullable(Security.getProvider("SunMSCAPI")).ifPresent(p->{ Security.removeProvider(p.getName()); Security.insertProviderAt(p, 1);});JVM现在报告以下(
-Djava.security.debug="provider,engine=SecureRandom"):
Provider: SecureRandom.Windows-PRNG algorithm from: SunMSCAPI
解决方案3:编程v2
受此想法的启发,以下代码仅插入了一个
SecureRandom服务,该服务是从现有
SunMSCAPI提供程序动态配置的,而无需明确依赖
sun.*类。这也避免了与提供
SunMSCAPI者所有服务的不分优先次序相关的潜在风险。
public interface WindowsPRNG { static void init() { String provider = "SunMSCAPI"; // original provider String type = "SecureRandom"; // service type String alg = "Windows-PRNG"; // algorithm String name = String.format("%s.%s", provider, type); // our provider name if (Security.getProvider(name) != null) return; // already registered Optional.ofNullable(Security.getProvider(provider)) // only on Windows .ifPresent(p-> Optional.ofNullable(p.getService(type, alg)) // should exist but who knows? .ifPresent(svc-> Security.insertProviderAt( // insert our provider with single SecureRandom service new Provider(name, p.getVersion(), null) {{ setProperty(String.format("%s.%s", type, alg), svc.getClassName()); }}, 1))); }}性能
<140 msec(而不是
5000+ msec)
细节
使用时有一个调用到
new SecureRandom()调用堆栈中的某个地方
URL.openConnection("https://...")它调用
getPrngAlgorithm()(请参阅SecureRandom:880)
这将返回
SecureRandom它找到的第一个提供程序。
出于测试目的,
URL.openConnection()可以使用以下命令代替to :
new SecureRandom().generateSeed(20);
免责声明
我不知道由提供商重新订购引起的任何负面影响。但是,可能会有一些,特别是考虑默认提供者选择算法。
无论如何,至少从理论上讲,从功能的角度来看,这对于应用程序应该是透明的。
更新2019-01-08
Windows 10(版本1803):无法再在任何最新的JDK上重现此问题(已从旧oracle 1.7.0_72到openjdk“ 12-ea”
2019-03-19进行了全部测试)。
看来这是Windows问题,已在最新的操作系统更新中修复。相关更新可能在最近的JRE版本中也可能没有发生。但是,即使使用我最旧的JDK 7 update72安装程序,也绝对无法受影响,并且绝对未进行任何修补,但我无法重现原始问题。
使用此解决方案时,性能仍然会有少量提升(平均大约为350毫秒),但是默认行为不再遭受无法忍受的5秒以上的惩罚。



