简体   繁体   English

慢速SecureRandom初始化

[英]Slow SecureRandom initialization

Suppose you do simple thing: 假设你做的很简单:

public class Main {
    public static void main(String[] args) {
        long started = System.currentTimeMillis();
        try {
            new URL(args[0]).openConnection();
        } catch (Exception ignore) {
        }
        System.out.println(System.currentTimeMillis() - started);
    }
}

Now run it with http://localhost as args[0] 现在使用http:// localhost作为args[0]运行它

It takes ~100 msec to complete. 完成需要~100 msec

Now try https://localhost 现在尝试https:// localhost

It takes 5000+ msec . 它需要5000+ msec

Now run the same thing on linux or in docker: 现在在linux或docker中运行相同的东西:

  • http: ~100 msec http: ~100 msec
  • https: ~350 msec https: ~350 msec

Why is this? 为什么是这样? Why such a huge difference between platforms? 为什么平台之间有这么大的差异? What can you do about it? 你能为这个做什么?

For long-running application servers and applications with their own long and heavy initialization sequence, these 5 seconds may not matter. 对于长时间运行的应用程序服务器和具有自己长而重的初始化序列的应用程序,这5秒可能无关紧要。

However, there are plenty of applications where this initial 5sec "hang" matters and may become frustrating... 然而,有很多应用程序,这个最初的5秒“挂起”很重要,可能会令人沮丧......

(Note: see also latest updates at the end of this answer) (注意:请参阅本答复末尾的最新更新)

Explanation 说明

Reason for this is default SecureRandom provider. 原因是默认的SecureRandom提供程序。

On Windows, there are 2 SecureRandom providers available: 在Windows上,有2个SecureRandom提供程序可用:

- provider=SUN, type=SecureRandom, algorithm=SHA1PRNG
- provider=SunMSCAPI, type=SecureRandom, algorithm=Windows-PRNG

On Linux (tested in Alpine docker with Oracle JDK 8u162): 在Linux上(使用Oracle JDK 8u162在Alpine docker中测试):

- provider=SUN, type=SecureRandom, algorithm=NativePRNG
- provider=SUN, type=SecureRandom, algorithm=SHA1PRNG
- provider=SUN, type=SecureRandom, algorithm=NativePRNGBlocking
- provider=SUN, type=SecureRandom, algorithm=NativePRNGNonBlocking

These are specified in jre/lib/security/java.security file. 这些在jre/lib/security/java.security文件中指定。

security.provider.1=sun.security.provider.Sun
...
security.provider.10=sun.security.mscapi.SunMSCAPI

By default, first SecureRandom provider is used. 默认情况下,使用第一个SecureRandom提供程序。 On Windows, the default one is sun.security.provider.Sun , and this implementation reports following when JVM is run with -Djava.security.debug="provider,engine=SecureRandom" : 在Windows上,默认的是sun.security.provider.Sun ,当使用-Djava.security.debug="provider,engine=SecureRandom"运行JVM时,此实现会报告以下内容:

Provider: SecureRandom.SHA1PRNG algorithm from: SUN
provider: Failed to use operating system seed generator: java.io.IOException: Required native CryptoAPI features not  available on this machine
provider: Using default threaded seed generator

And the default threaded seed generator is very slow. 默认的线程种子生成器非常慢。

You need to use SunMSCAPI provider. 您需要使用SunMSCAPI提供程序。

Solution 1: Configuration 解决方案1:配置

Reorder providers in configuration: 在配置中重新排序提供商:

Edit jre/lib/security/java.security : 编辑jre/lib/security/java.security

security.provider.1=sun.security.mscapi.SunMSCAPI
...
security.provider.10=sun.security.provider.Sun

I am not aware this can be done via system properties. 我不知道这可以通过系统属性来完成。

Or maybe yes, using -Djava.security.properties (untested, see this ) 或许是的,使用-Djava.security.properties (未经测试, 请参阅此内容

Solution 2: Programmatic 解决方案2:程序化

Reorder providers programmatically: 以编程方式重新排序提供商

Optional.ofNullable(Security.getProvider("SunMSCAPI")).ifPresent(p->{
    Security.removeProvider(p.getName());
    Security.insertProviderAt(p, 1);
});

JVM now reports following ( -Djava.security.debug="provider,engine=SecureRandom" ): JVM现在报告以下( -Djava.security.debug="provider,engine=SecureRandom" ):

Provider: SecureRandom.Windows-PRNG algorithm from: SunMSCAPI

Solution 3: Programmatic v2 解决方案3:程序化v2

Inspired by this idea , following piece of code inserts only a single SecureRandom service, configured dynamically from existing SunMSCAPI provider without the explicit reliance on sun.* classes. 受此想法的启发,下面的一段代码只插入一个SecureRandom服务,该服务是从现有SunMSCAPI提供程序动态配置的,而不依赖于sun.*类。 This also avoids the potential risks associated with indiscriminate prioritization of all services of SunMSCAPI provider. 这也避免了与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)));
    }

}

Performance 性能

<140 msec (instead of 5000+ msec ) <140 msec (而不是5000+ msec

Details 细节

There is a call to new SecureRandom() somewhere down the call stack when you use URL.openConnection("https://...") 当您使用URL.openConnection("https://...")时,在调用堆栈的某处调用new SecureRandom() URL.openConnection("https://...")

It calls getPrngAlgorithm() (see SecureRandom:880 ) 它调用getPrngAlgorithm() (参见SecureRandom:880

And this returns first SecureRandom provider it finds. 这将返回它找到的第一个SecureRandom提供程序。

For testing purposes, call to URL.openConnection() can be replaced with this: 出于测试目的,可以URL.openConnection()调用替换为:

new SecureRandom().generateSeed(20);

Disclaimer 放弃

I am not aware of any negative side effects caused by providers reordering. 我不知道提供商重新排序造成的任何负面影响。 However, there may be some, especially considering default provider selection algorithm. 但是,可能有一些,特别是考虑默认提供商选择算法。

Anyway, at least in theory, from functional point of view this should be transparent to application. 无论如何,至少在理论上,从功能的角度来看,这应该对应用是透明的。

Update 2019-01-08 更新2019-01-08

Windows 10 (version 1803): Cannot reproduce this issue anymore on any of latest JDKs (tested all from old oracle 1.7.0_72 up to openjdk "12-ea" 2019-03-19). Windows 10(版本1803):无法再在任何最新的JDK上重现此问题(从旧的oracle 1.7.0_72到openjdk“12-ea”2019-03-19都进行了测试)。

It looks like it was Windows issue, fixed in latest OS updates. 看起来它是Windows问题,修复了最新的操作系统更新。 Related updates may or may not have taken place in recent JRE releases, too. 相关更新可能也可能没有在最近的JRE版本中发生。 However, I cannot reproduce the original issue even with my oldest JDK 7 update 72 installation which was definitelly affected, and definitelly not patched in any way. 但是,我无法重现原始问题,即使我最老的JDK 7更新72安装受到了影响,并且definitelly没有以任何方式修补。

There are still minor performance gains when using this solution (cca 350 msec on average) but the default behavior no longer suffers the intolerable 5+ seconds penalty. 使用此解决方案时仍然有轻微的性能提升(平均cca 350毫秒),但默认行为不再受到难以忍受的5秒以上的惩罚。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM