安全随机初始化缓慢

2022-09-04 02:07:30

假设你做了简单的事情:

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

现在将其 http://localhostargs[0]

它需要完成。~100 msec

现在试试 https://localhost

它需要.5000+ msec

现在在 linux 或 docker 上运行相同的内容:

  • 网址:~100 msec
  • 网址:~350 msec

这是为什么呢?为什么平台之间有如此巨大的差异?你能做些什么呢?

对于长时间运行的应用程序服务器和具有自己冗长而繁重的初始化序列的应用程序,这 5 秒可能无关紧要。

但是,在很多应用程序中,最初的5秒“挂起”很重要,可能会令人沮丧......


答案 1

(注意:另请参阅本答案末尾的最新更新)

解释

这样做的原因是默认提供程序。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

默认情况下,使用第一个提供程序。在 Windows 上,缺省值为 ,当使用 以下命令运行 JVM 时,此实现将报告如下:SecureRandomsun.security.provider.Sun-Djava.security.debug="provider,engine=SecureRandom"

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

默认的线程种子生成器非常慢。

您需要使用提供程序。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

这个想法的启发,下面的代码段只插入一个服务,从现有提供程序动态配置,而不显式依赖类。这也避免了与不加区别地确定提供商所有服务的优先级相关的潜在风险。SecureRandomSunMSCAPIsun.*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://...")

它调用(请参阅 SecureRandom:880getPrngAlgorithm())

这将返回它找到的第一个提供程序。SecureRandom

出于测试目的,调用 可以替换为:URL.openConnection()

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 update 72安装,我也无法重现原始问题,该安装肯定受到了影响,并且肯定没有以任何方式进行修补。

使用此解决方案时,仍有轻微的性能提升(平均 cca 为 350 毫秒),但默认行为不再遭受无法忍受的 5 秒以上惩罚。


答案 2

推荐