服务响应时间慢:Java SecureRandom和/ dev / random - java

我正在尝试调试Tomcat上部署的应用程序提供的一些慢速响应。
现在,我主要关注SecureRandom/dev/random(其他一些可能的原因已被调查并排除)。
模式如下:

  • 第一个调用恰好在Tomcat重新启动后(即使请求在启动后4分钟到达)也仅需 30.0 xy秒
  • 稍后,某些调用恰好需要 15.0 pq秒(没有可以建立的特定模式,pq是TP99中花费的时间)。
  • 服务调用涉及加密和解密(AES / ECB / PKCS5Padding)。

    SecureRandom初始化/重新填充是否有可能导致这种情况?

    (尽管在catalina.log中写了一个日志,上面写着"Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [28,760] milliseconds.")

    另外,为了检查是否正在使用/dev/random/dev/urandom,我使用了this question的测试。令我惊讶的是,与链接问题中发生的情况不同,我没有看到它们中的任何一个。
    这些是strace日志的最后几行:

    3561  lstat("/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre/lib/jsse.jar", {st_mode=S_IFREG|0644, st_size=258525, ...}) = 0
    3561  open("/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre/lib/jsse.jar", O_RDONLY) = 6
    3561  stat("/dev/random", {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
    3561  stat("/dev/urandom", {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
    3561  open("/dev/random", O_RDONLY)     = 7
    3561  open("/dev/urandom", O_RDONLY)    = 8
    3561  unlink("/tmp/hsperfdata_xxxx/3560") = 0
    

    那么,什么用于播种SecureRandom?

    fyi,java -version

    java version "1.6.0_32"
    OpenJDK Runtime Environment (IcedTea6 1.13.4) (rhel-7.1.13.4.el6_5-x86_64)
    OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
    

    参考方案

    我无法检查您的OpenJDK具体版本,但可以检查jdk6-b33。

    SecureRandom使用SeedGenerator获取种子字节

    public byte[] engineGenerateSeed(int numBytes) {
        byte[] b = new byte[numBytes];
        SeedGenerator.generateSeed(b);
        return b;
    }
    

    SeedGenerator从SunEntries获取seedSource(字符串)

    String egdSource = SunEntries.getSeedSource();
    

    SunEntries尝试首先从系统属性java.security.egd获取源,如果找不到,则尝试从securerandom.source属性文件获取java.security属性,如果找不到该属性,则返回空白字符串。

    // name of the *System* property, takes precedence over PROP_RNDSOURCE
    private final static String PROP_EGD = "java.security.egd";
    // name of the *Security* property
    private final static String PROP_RNDSOURCE = "securerandom.source";
    
    final static String URL_DEV_RANDOM = "file:/dev/random";
    final static String URL_DEV_URANDOM = "file:/dev/urandom";
    
    private static final String seedSource;
    
    static {
        seedSource = AccessController.doPrivileged(
                new PrivilegedAction<String>() {
    
            public String run() {
                String egdSource = System.getProperty(PROP_EGD, "");
                if (egdSource.length() != 0) {
                    return egdSource;
                }
                egdSource = Security.getProperty(PROP_RNDSOURCE);
                if (egdSource == null) {
                    return "";
                }
                return egdSource;
            }
        });
    }
    

    SeedGenerator检查此值以初始化实例

    // Static instance is created at link time
    private static SeedGenerator instance;
    
    private static final Debug debug = Debug.getInstance("provider");
    
    final static String URL_DEV_RANDOM = SunEntries.URL_DEV_RANDOM;
    final static String URL_DEV_URANDOM = SunEntries.URL_DEV_URANDOM;
    
    // Static initializer to hook in selected or best performing generator
    static {
        String egdSource = SunEntries.getSeedSource();
    
        // Try the URL specifying the source
        // e.g. file:/dev/random
        //
        // The URL file:/dev/random or file:/dev/urandom is used to indicate
        // the SeedGenerator using OS support, if available.
        // On Windows, the causes MS CryptoAPI to be used.
        // On Solaris and Linux, this is the identical to using
        // URLSeedGenerator to read from /dev/random
    
        if (egdSource.equals(URL_DEV_RANDOM) || egdSource.equals(URL_DEV_URANDOM)) {
            try {
                instance = new NativeSeedGenerator();
                if (debug != null) {
                    debug.println("Using operating system seed generator");
                }
            } catch (IOException e) {
                if (debug != null) {
                    debug.println("Failed to use operating system seed "
                                  + "generator: " + e.toString());
                }
            }
        } else if (egdSource.length() != 0) {
            try {
                instance = new URLSeedGenerator(egdSource);
                if (debug != null) {
                    debug.println("Using URL seed generator reading from "
                                  + egdSource);
                }
            } catch (IOException e) {
                if (debug != null)
                    debug.println("Failed to create seed generator with "
                                  + egdSource + ": " + e.toString());
            }
        }
    
        // Fall back to ThreadedSeedGenerator
        if (instance == null) {
            if (debug != null) {
                debug.println("Using default threaded seed generator");
            }
            instance = new ThreadedSeedGenerator();
        }
    }
    

    如果来源是

    final static String URL_DEV_RANDOM = "file:/dev/random";
    

    要么

    final static String URL_DEV_URANDOM = "file:/dev/urandom"
    

    在Windows上使用NativeSeedGenerator尝试在Linux上使用本机CryptoAPI该类只是扩展了SeedGenerator.URLSeedGenerator

    package sun.security.provider;
    
    import java.io.IOException;
    
    /**
     * Native seed generator for Unix systems. Inherit everything from
     * URLSeedGenerator.
     *
     */
    class NativeSeedGenerator extends SeedGenerator.URLSeedGenerator {
    
        NativeSeedGenerator() throws IOException {
            super();
        }
    
    }
    

    并调用默认情况下加载/dev/random的超类构造函数

    URLSeedGenerator() throws IOException {
        this(SeedGenerator.URL_DEV_RANDOM);
    }
    

    因此,在没有在系统属性/dev/random或安全属性文件的java.security.egd属性中未设置其他值之前,OpenJDK默认使用securerandom.source

    如果要使用strace查看读取结果,可以更改命令行并添加trace=open,read表达式

    sudo strace -o a.strace -f -e trace=open,read java class
    

    您会看到类似的内容(我使用Oracle JDK 6进行了测试)

    13225 open("/dev/random", O_RDONLY)     = 8
    13225 read(8, "@", 1)                   = 1
    13225 read(3, "PK\3\4\n\0\0\0\0\0RyzB\36\320\267\325u\4\0\0u\4\0\0 \0\0\0", 30) = 30
    ....
    ....
    

    如果您在启动过程中遇到延迟,请参阅“Tomcat Wiki”部分中有关加快启动速度的建议,使用/ dev / urandom之类的非阻塞熵源

    更多信息:https://wiki.apache.org/tomcat/HowTo/FasterStartUp#Entropy_Source

    希望这可以帮助。

    Java-搜索字符串数组中的字符串 - java

    在Java中,我们是否有任何方法可以发现特定字符串是字符串数组的一部分。我可以避免出现一个循环。例如String [] array = {"AA","BB","CC" }; string x = "BB" 我想要一个if (some condition to tell wheth…

    产生6位数的随机数 - java

    我只想生成6位数的随机数,范围应从000000到999999。new Random().nextInt(999999)返回我的电话号码,但不是6位数字。 参考方案 就这么简单,您可以使用您的代码,并在此处做一件事String.format("%06d", number); 这将以字符串格式返回您的数字,因此“0”将为“000000”。这是…

    Java Scanner读取文件的奇怪行为 - java

    因此,在使用Scanner类从文件读取内容时,我遇到了一个有趣的问题。基本上,我试图从目录中读取解析应用程序生成的多个输出文件,以计算一些准确性指标。基本上,我的代码只是遍历目录中的每个文件,并使用扫描仪将其打开以处理内容。无论出于何种原因,扫描程序都不会读取其中的一些文件(所有UTF-8编码)。即使文件不是空的,scanner.hasNextLine()在…

    Java Globbing模式以匹配目录和文件 - java

    我正在使用递归函数遍历根目录下的文件。我只想提取*.txt文件,但不想排除目录。现在,我的代码如下所示:val stream = Files.newDirectoryStream(head, "*.txt") 但是这样做将不会匹配任何目录,并且返回的iterator()是False。我使用的是Mac,所以我不想包含的噪音文件是.DS_ST…

    直接读取Zip文件中的文件-Java - java

    我的情况是我有一个包含一些文件(txt,png,...)的zip文件,我想直接按它们的名称读取它,我已经测试了以下代码,但没有结果(NullPointerExcepion):InputStream in = Main.class.getResourceAsStream("/resouces/zipfile/test.txt"); Buff…