【问题标题】:Slow service response Times : Java SecureRandom & /dev/random [duplicate]服务响应时间慢:Java SecureRandom & /dev/random [重复]
【发布时间】:2015-01-26 05:42:36
【问题描述】:

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

  • 第一次调用恰好在 Tomcat 重启后 30.0xy 秒(即使请求在启动后 4 分钟到达)
  • 后来,有些调用恰好需要 15.0pq 秒(我无法确定具体的模式,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?

仅供参考,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)

【问题讨论】:

  • 你可以尝试一个小的应用程序来创建一个新的随机生成(使用new SecureRandom),然后从中读取一些字节,看看是否从urandom读取。确保您的目标是相同的 Java 运行时,并检查 java.security.egd 属性是否未使用 java -D 为 Tomcat 设置。
  • 我确实运行了一个示例程序(代码取自链接上的问题)。它既不是从 /dev/random 也不是从 /dev/urandom 读取的,如问题所附的 strace 日志中所示。
  • 您能否也检查一下jre/lib/security/java.security 并检查securerandom.source 是如何定义的?
  • 谢谢@owlstead。它是 /dev/urandom。
  • 你能试试/dev/./urandom看看会发生什么吗?

标签: java tomcat random aes strace


【解决方案1】:

问题不是 SecureRandom 本身,而是 /dev/random 如果没有足够的数据会阻塞。您可以使用 urandom 代替,但如果您需要加密强的随机种子,这可能不是一个好主意。 在无头 Linux 系统上,您可以安装 haveged 守护程序。这使 /dev/random 充满了足够的数据,这样调用就不必等待生成所需的熵。 我已经在 Debian Aws 实例上完成了此操作,并观察到 ​​SecureRandom generateBytes 调用从 25 秒下降到亚毫秒(Openjdk 1.7 的东西,不记得具体是什么版本)。

【讨论】:

    【解决方案2】:

    我无法检查您的 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获取源,如果找不到则尝试从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<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 上尝试使用本机CryptoAPI,在Linux 上该类只是扩展了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

    希望这会有所帮助。

    【讨论】:

    • 感谢read 跟踪,我确认此JVM 中正在使用\dev\urandom。没有 egd 覆盖,securerandom.source 也是\dev\urandom。因此,这排除了 SecureRandom 作为原因的可能性,我将不得不查看其他可能的原因。
    猜你喜欢
    • 2011-06-16
    • 2011-05-18
    • 2017-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-18
    • 1970-01-01
    相关资源
    最近更新 更多