当前位置: 首页 > 面试题库 >

缓慢的SecureRandom初始化

古畅
2023-03-14
问题内容

假设您做简单的事情:

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://本地主机作为args[0]

它需要~100 msec完成。

现在尝试https:// localhost

这需要5000+ msec

现在在linux或docker中运行相同的东西:

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

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

对于长时间运行的应用程序服务器以及具有各自冗长且繁琐的初始化序列的应用程序,这5秒可能无关紧要。

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


问题答案:

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

说明

原因是默认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: 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

受此想法的启发,以下代码仅插入了一个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秒以上的惩罚。



 类似资料:
  • 我发现初始化模型的速度非常慢。完成需要40秒! 我的代码包含两个主要部分:1)CSV数据读取器将首先运行以加载数据,这需要不到1秒的时间来完成35000行的读取和处理(请参见下面的第一部分代码);2) 随后初始化代理和边。特别是,Edge初始化将使用CSV reader中加载的数据(参见下面的第二部分代码)。 第一部分:CSVReader代码 这是CSVReader使用的输入文件: 第二部分:边缘

  • 问题内容: 如果你想要Java中具有加密强度的随机数,请使用。不幸的是,可能会很慢。如果在Linux上使用,它将阻止等待足够的熵建立。你如何避免性能下降? 有没有人使用罕见数学作为解决此问题的方法? 任何人都可以确认在JDK 6中已经解决了此性能问题吗? 问题答案: 如果你想要真正的随机数据,那么不幸的是你必须等待它。这包括 的种子。罕见的数学无法以比更快的速度收集真正的随机数据,尽管它可以连接到

  • 问题内容: 我在CentOS 6.6 32位和openJDK7上使用Tomcat 7.0.57。当我在服务器(生产环境)上启动14个不同的Tomcat实例时,它们中的许多启动时间都太多了。 这是启动日志的一部分,它告诉我所有时间都花在哪里 解决此问题的最佳实践/解决方案是什么? 谢谢! 问题答案: 安全的随机调用可能会被阻止,因为没有足够的熵来将它们提供给/ dev / random。 如果你有线

  • 我正在用Apache CXF 2.7.8开发web服务。我使用wsdl2java来生成SEI和实现。有数百个web服务endpoint。 我使用嵌入式码头。 当服务器启动时,初始化每个endpoint需要几个小时。 我曾经使用 Endpoint.publish(地址、实现器)方法发布endpoint,但我发现通过这种方式,可以为每个服务创建一个 JAXBContext 实例。我修复了为每个服务使用

  • 问题内容: 我们的团队正在使用SecureRandom生成密钥对列表(将SecureRandom传递给KeyPairGenerator)。对于以下两种选择中的哪一种,我们无法达成共识: 每次需要生成密钥对时都创建一个新实例 初始化静态实例并将其用于所有密钥对 通常哪种方法更好, 为什么 ? 添加:我的直觉是第二种选择更安全。但我唯一的论点是基于以下假设的理论攻击:假随机性是从当前时间戳派生的:某人

  • 大家好,我是Kotlin语言的新手,在运行Hello World代码的过程中遇到了一些错误。 无法为初始化脚本'C:\User\HP\AppData\Local\Temp\wrapper_init.gradle'打开初始化泛型类缓存(C:\User\HP. gradle\cache\6.8\script\5mjee5vr2mabvexqryui51pg)。 缺陷源单元“BuildScript”中的