服务响应速度慢时间:Java SecureRandom和/ dev / random

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

  • Tomcat重新启动后第一次调用需要30.0 xy秒(即使请求在启动后4分钟到达)
  • 之后,一些调用只需要15.0 pq秒(没有我可以建立的特定模式, pq是TP99中所用的时间近似时间)

服务调用涉及加密和解密( AES / ECB / PKCS5Padding )。

SecureRandom init / repopulating是否可能导致这种情况发生?

(尽管有一个用catalina.log编写的日志说"Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [28,760] milliseconds."

另外,为了检查是否正在使用/dev/random/dev/urandom ,我使用了这个问题的测试。 令我惊讶的是,我没有看到其中任何一个的读取,不像它在链接问题中发生的那样。 这些是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)

 String egdSource = SunEntries.getSeedSource(); 

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

 // 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() { 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 // eg 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" 

使用NativeSeedGenerator ,在Windows上尝试在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); } 

因此,OpenJDK默认使用/dev/random ,直到您不在系统属性java.security.egd或安全属性文件的属性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

希望这可以帮助。

问题不是SecureRandom本身,而是/ dev / random阻塞,如果它没有足够的数据。 您可以使用urandom,但如果您需要加密强随机种子,这可能不是一个好主意。 在无头Linux系统上,您可以安装hasged守护程序。 这使得/ dev / random保持足够的数据,以便调用不必等待生成所需的熵。 我在Debian Aws实例上做了这个,看着SecureRandom generateBytes调用从25秒下降到亚毫秒(Openjdk 1.7的东西,不记得具体是什么版本)。