Java Secure Random

Java标准库中的SecureRandom提供了一个密码学意义上的强随机数生成器。作为一个程序员,如果我们在JVM里出于安全考虑,想获取一个“真正随机”的数字,则很有可能将SecureRandom纳入考虑范围之内。 然而使用它需要十分的小心。

对SecureRandom起兴趣是因为一次对网络程序的排错,账号的登录过程十分缓慢。日志十分正常,没有任何警告或者错误。所以最初怀疑是某种逻辑错误导致的死循环。

jstack的thread dump如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
"main" #1 prio=5 os_prio=0 cpu=2745.71ms elapsed=56.22s tid=0x00007f2ec4014800 nid=0x2494 runnable  [0x00007f2ecde32000]
   java.lang.Thread.State: RUNNABLE
	at java.io.FileInputStream.readBytes(java.base@11.0.9.1/Native Method)
	at java.io.FileInputStream.read(java.base@11.0.9.1/FileInputStream.java:279)
	at java.io.FilterInputStream.read(java.base@11.0.9.1/FilterInputStream.java:133)
	at sun.security.provider.NativePRNG$RandomIO.readFully(java.base@11.0.9.1/NativePRNG.java:424)
	at sun.security.provider.NativePRNG$RandomIO.implGenerateSeed(java.base@11.0.9.1/NativePRNG.java:441)
	- locked <0x00000000ed00c680> (a java.lang.Object)
	at sun.security.provider.NativePRNG.engineGenerateSeed(java.base@11.0.9.1/NativePRNG.java:226)
	at java.security.SecureRandom.generateSeed(java.base@11.0.9.1/SecureRandom.java:857)
	at java.security.SecureRandom.getSeed(java.base@11.0.9.1/SecureRandom.java:840)
	at net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin15Kt.get_mpasswd(WtLogin15.kt:136)
	at net.mamoe.mirai.internal.network.QQAndroidClient.<init>(QQAndroidClient.kt:268)
	at net.mamoe.mirai.internal.network.QQAndroidClient.<init>(QQAndroidClient.kt:72)
	at net.mamoe.mirai.internal.QQAndroidBot.initClient(QQAndroidBot.kt:62)
	at net.mamoe.mirai.internal.QQAndroidBot.<init>(QQAndroidBot.kt:59)
	at net.mamoe.mirai.internal.BotFactoryImpl.newBot(BotFactory.kt:34)
	at net.mamoe.mirai.BotFactory$INSTANCE.newBot(BotFactory.kt:115)
	at net.mamoe.mirai.console.MiraiConsole$INSTANCE.addBotImpl(MiraiConsole.kt:163)
	at net.mamoe.mirai.console.MiraiConsole$INSTANCE.addBot(MiraiConsole.kt:125)
	at net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge$doStart$11$1.invokeSuspend(MiraiConsoleImplementationBridge.kt:241)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:86)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:61)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge.doStart$mirai_console(MiraiConsoleImplementationBridge.kt:214)
	at net.mamoe.mirai.console.MiraiConsoleImplementation$Companion.start(MiraiConsoleImplementation.kt:209)
	at net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader.startAsDaemon(MiraiConsoleTerminalLoader.kt:153)
	at net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader.startAsDaemon$default(MiraiConsoleTerminalLoader.kt:152)
	at net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader.main(MiraiConsoleTerminalLoader.kt:48)

   Locked ownable synchronizers:
	- <0x00000000ed5cf040> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)

可以看到程序阻塞在了java.security.SecureRandomjava.io.FileInputStream.readBytes

看起来我们的 SecureRandom 在读取什么文件?如果你了解类Unix系统的话就不难猜出这个文件是 /dev/random。在GNU Linux上读取 /dev/random 这个设备文件就会得到随机的字节(试试 cat /dev/random?)。Linux系统会借助用户的鼠标输入、键盘输入、硬盘读取等环境噪声生成随机数。Linux将环境噪声保存在熵池中,使用cat /proc/sys/kernel/random/entropy_avail可以查看熵池的大小。显然在服务器环境下这些噪声较为稀少,这就导致了在服务器上 /dev/random 随机数生成缓慢。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Built with Hugo
主题 StackJimmy 设计