我正在尝试调试部署在 Tomcat 上的应用程序提供的一些缓慢响应。
现在我专注于 SecureRandom
和 /dev/random
(其他一些可能的原因已被调查并排除)。
模式如下:
- 第一次调用在 Tomcat 重启后正好 30.0xy 秒(即使请求在启动后 4 分钟到达)
- 后来,一些调用恰好花费了 15.0pq 秒(我无法确定具体的模式,pq 是 TP99 中花费的近似时间)
服务调用涉及加解密(AES/ECB/PKCS5Padding)。
SecureRandom init/repopulating 是否可能导致这种情况?
(尽管在 catalina.log 中有一个日志写着 “使用 [SHA1PRNG] 为 session ID 生成创建 SecureRandom 实例花费了 [28,760] 毫秒。”
)
此外,为了检查是否正在使用/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?
仅供引用,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"
使用 NativeSeedGenerator
,在 Windows 上尝试在 Linux 上使用 native 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
的父类(super class)构造函数
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
希望这对您有所帮助。