文章目录

  • java.util.Random
  • java.Security.SecureRandom
  • /dev/random 与 /dev/urandom
  • 资料

Java 里提供了一些用于生成随机数的工具类,这里分析一下其实现原理,以及他们之间的区别、使用场景。

java.util.Random

Random 是比较常用的随机数生成类,它的基本信息在类的注释里都写到了,下面是 JDK8 里该类的注释:

/*** An instance of this class is used to generate a stream of* pseudorandom numbers. The class uses a 48-bit seed, which is* modified using a linear congruential formula. (See Donald Knuth,* <i>The Art of Computer Programming, Volume 2</i>, Section 3.2.1.)* <p>* If two instances of {@code Random} are created with the same* seed, and the same sequence of method calls is made for each, they* will generate and return identical sequences of numbers. In order to* guarantee this property, particular algorithms are specified for the* class {@code Random}. Java implementations must use all the algorithms* shown here for the class {@code Random}, for the sake of absolute* portability of Java code. However, subclasses of class {@code Random}* are permitted to use other algorithms, so long as they adhere to the* general contracts for all the methods.* <p>* The algorithms implemented by class {@code Random} use a* {@code protected} utility method that on each invocation can supply* up to 32 pseudorandomly generated bits.* <p>* Many applications will find the method {@link Math#random} simpler to use.** <p>Instances of {@code java.util.Random} are threadsafe.* However, the concurrent use of the same {@code java.util.Random}* instance across threads may encounter contention and consequent* poor performance. Consider instead using* {@link java.util.concurrent.ThreadLocalRandom} in multithreaded* designs.** <p>Instances of {@code java.util.Random} are not cryptographically* secure.  Consider instead using {@link java.security.SecureRandom} to* get a cryptographically secure pseudo-random number generator for use* by security-sensitive applications.** @author  Frank Yellin* @since   1.0*/

翻译一下,主要有以下几点:

  1. Random 类使用线性同余法 linear congruential formula 来生成伪随机数。
  2. 两个 Random 实例,如果使用相同的种子 seed,那他们产生的随机数序列也是一样的。
  3. Random 是线程安全的,你的程序如果对性能要求比较高的话,推荐使用 ThreadLocalRandom。
  4. Random 不是密码学安全的,加密相关的推荐使用 SecureRandom。

Random 的基本用法如下所示:

Random random = new Random();
int r = random.nextInt(); // 生成一个随机数

从下面的源码中可以看到,Random 的默认使用当前系统时钟来生成种子 seed。

    private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);public Random() {this(seedUniquifier() ^ System.nanoTime());}public Random(long seed) {if (getClass() == Random.class)this.seed = new AtomicLong(initialScramble(seed));else {// subclass might have overriden setSeedthis.seed = new AtomicLong();setSeed(seed);}}private static long seedUniquifier() {for (;;) {long current = seedUniquifier.get();long next = current * 181783497276652981L;if (seedUniquifier.compareAndSet(current, next))return next;}}

java.Security.SecureRandom

介绍 Random 类时提到过,要生成加密基本的随机数应该使用 SecureRandom 类,该类信息如下所示:

/*** This class provides a cryptographically strong random number* generator (RNG).** <p>A cryptographically strong random number* minimally complies with the statistical random number generator tests* specified in <a href="http://csrc.nist.gov/cryptval/140-2.htm">* <i>FIPS 140-2, Security Requirements for Cryptographic Modules</i></a>,* section 4.9.1.* Additionally, SecureRandom must produce non-deterministic output.* Therefore any seed material passed to a SecureRandom object must be* unpredictable, and all SecureRandom output sequences must be* cryptographically strong, as described in* <a href="http://www.ietf.org/rfc/rfc1750.txt">* <i>RFC 1750: Randomness Recommendations for Security</i></a>.** <p>A caller obtains a SecureRandom instance via the* no-argument constructor or one of the {@code getInstance} methods:** <pre>*      SecureRandom random = new SecureRandom();* </pre>** <p> Many SecureRandom implementations are in the form of a pseudo-random* number generator (PRNG), which means they use a deterministic algorithm* to produce a pseudo-random sequence from a true random seed.* Other implementations may produce true random numbers,* and yet others may use a combination of both techniques.** <p> Typical callers of SecureRandom invoke the following methods* to retrieve random bytes:** <pre>*      SecureRandom random = new SecureRandom();*      byte bytes[] = new byte[20];*      random.nextBytes(bytes);* </pre>** <p> Callers may also invoke the {@code generateSeed} method* to generate a given number of seed bytes (to seed other random number* generators, for example):* <pre>*      byte seed[] = random.generateSeed(20);* </pre>** Note: Depending on the implementation, the {@code generateSeed} and* {@code nextBytes} methods may block as entropy is being gathered,* for example, if they need to read from /dev/random on various Unix-like* operating systems.*/

主要有以下几点:

  1. 该类提供了能满足加密要求的强随机数生成器。
  2. 传递给 SecureRandom 种子必须是不可预测的,seed 使用不当引发的安全漏洞可以看看 比特币电子钱包漏洞。
    • 一般使用默认的种子生成策略就行,对应 Linux 里面就是 /dev/random 和 /dev/urandom。其实现原理是:操作系统收集了一些随机事件,比如鼠标点击,键盘点击等等,SecureRandom 使用这些随机事件作为种子。
    • 使用 /dev/random 来生成种子时,可能会因为熵不够而阻塞,性能比较差。

SecureRandom 用法如下所示:

SecureRandom random = new SecureRandom();
byte[] data = random.nextBytes(16);

下面我们看看其内部实现:

    synchronized public void nextBytes(byte[] bytes) {secureRandomSpi.engineNextBytes(bytes);}public SecureRandom() {super(0);getDefaultPRNG(false, null);}private void getDefaultPRNG(boolean setSeed, byte[] seed) {String prng = getPrngAlgorithm();if (prng == null) {// bummer, get the SUN implementationprng = "SHA1PRNG";this.secureRandomSpi = new sun.security.provider.SecureRandom();this.provider = Providers.getSunProvider();if (setSeed) {this.secureRandomSpi.engineSetSeed(seed);}} else {try {SecureRandom random = SecureRandom.getInstance(prng);this.secureRandomSpi = random.getSecureRandomSpi();this.provider = random.getProvider();if (setSeed) {this.secureRandomSpi.engineSetSeed(seed);}} catch (NoSuchAlgorithmException nsae) {// never happens, because we made sure the algorithm existsthrow new RuntimeException(nsae);}}if (getClass() == SecureRandom.class) {this.algorithm = prng;}}

在 mac 环境下使用 JDK8 测试时发现,默认使用了 NativePRNG 而非 SHA1PRNG,但是 NativePRNG 其实还是在 sun.security.provider.SecureRandom 的基础上做了一些封装。

在 sun.security.provider.SeedGenerator 类里,可以看到 seed 是利用 /dev/random 或 /dev/urandom 来生成的,启动应用程序时可以通过参数 -Djava.security.egd=file:/dev/urandom 来指定 seed 源。

    static {String var0 = SunEntries.getSeedSource();if (!var0.equals("file:/dev/random") && !var0.equals("file:/dev/urandom")) {if (var0.length() != 0) {try {instance = new SeedGenerator.URLSeedGenerator(var0);if (debug != null) {debug.println("Using URL seed generator reading from " + var0);}} catch (IOException var2) {if (debug != null) {debug.println("Failed to create seed generator with " + var0 + ": " + var2.toString());}}}} else {try {instance = new NativeSeedGenerator(var0);if (debug != null) {debug.println("Using operating system seed generator" + var0);}} catch (IOException var3) {if (debug != null) {debug.println("Failed to use operating system seed generator: " + var3.toString());}}}if (instance == null) {if (debug != null) {debug.println("Using default threaded seed generator");}instance = new SeedGenerator.ThreadedSeedGenerator();}}

在 Random 类里,多个实例设置相同的seed,产生的随机数序列也是一样的。而 SecureRandom 则不同,运行下面的代码:

public class RandomTest {public static void main(String[] args) {byte[] seed = "hello".getBytes();for (int i = 0; i < 10; ++i) {SecureRandom secureRandom = new SecureRandom(seed);System.out.println(secureRandom.nextInt());}}
}

输出如下所示,每次运行产生的随机数都不一样。

-2105877601
1151182748
1329080810
-617594950
2094315881
-1649759687
-1360561033
-653424535
-927058354
-1577199965

为什么呢?因为 engineSetSeed 方法设置 seed 时调用的是静态实例 INSTANCE 的 implSetSeed 方法,该方法通过 getMixedRandom 得到的 SecureRandom 来设置 seed,而这个 SecureRandom 初始化种子是系统的。

    private static final NativePRNG.RandomIO INSTANCE;// in NativePRNGprotected void engineSetSeed(byte[] var1) {INSTANCE.implSetSeed(var1);}private void implSetSeed(byte[] var1) {Object var2 = this.LOCK_SET_SEED;synchronized(this.LOCK_SET_SEED) {if (!this.seedOutInitialized) {this.seedOutInitialized = true;this.seedOut = (OutputStream)AccessController.doPrivileged(new PrivilegedAction<OutputStream>() {public OutputStream run() {try {return new FileOutputStream(RandomIO.this.seedFile, true);} catch (Exception var2) {return null;}}});}if (this.seedOut != null) {try {this.seedOut.write(var1);} catch (IOException var5) {throw new ProviderException("setSeed() failed", var5);}}this.getMixRandom().engineSetSeed(var1);}}private SecureRandom getMixRandom() {SecureRandom var1 = this.mixRandom;if (var1 == null) {Object var2 = this.LOCK_GET_BYTES;synchronized(this.LOCK_GET_BYTES) {var1 = this.mixRandom;if (var1 == null) {var1 = new SecureRandom();try {byte[] var3 = new byte[20];readFully(this.nextIn, var3);var1.engineSetSeed(var3);} catch (IOException var5) {throw new ProviderException("init failed", var5);}this.mixRandom = var1;}}}return var1;}

在 sun.security.provider.SecureRandom.engineSetSeed 方法里,新种子的生成不仅和刚设置的 seed 有关,也和原来的种子(系统产生的 seed)有关。

 // in sun.security.provider.SecureRandom public synchronized void engineSetSeed(byte[] var1) {if (this.state != null) {this.digest.update(this.state);for(int var2 = 0; var2 < this.state.length; ++var2) {this.state[var2] = 0;}}this.state = this.digest.digest(var1);}

/dev/random 与 /dev/urandom

在 Linux 操作系统中,有一个特殊的设备文件 /dev/random,可以用作随机数发生器或伪随机数发生器。

在读取时,/dev/random 设备会返回小于熵池噪声总数的随机字节。/dev/random 可生成高随机性的公钥或一次性密码本。若熵池空了,对/dev/random的读操作将会被阻塞,直到从别的设备中收集到了足够的环境噪声为止。

当然你也可以设置成不堵塞,当你在 open 的时候设置参数 O_NONBLOCK, 但是当你read 的时候,如果熵池空了,会返回 -1。

/dev/random 的一个副本是 /dev/urandom (“unlocked”,非阻塞的随机数发生器),它会重复使用熵池中的数据以产生伪随机数据。这表示对/dev/urandom的读取操作不会产生阻塞,但其输出的熵可能小于 /dev/random 的。它可以作为生成较低强度密码的伪随机数生成器,不建议用于生成高强度长期密码。

资料

  1. 随机算法线性同余法的理解

Java 随机数生成器 Random SecureRandom 原理分析相关推荐

  1. Java 随机数生成器 Random SecureRandom

    Java 里提供了一些用于生成随机数的工具类,这里分析一下其实现原理,以及他们之间的区别.使用场景. java.util.Random Random 是比较常用的随机数生成类,它的基本信息在类的注释里 ...

  2. java并发包线程池原理分析锁的深度化

    java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素 ...

  3. java—随机数生成器

    随机数生成器 通过简单的Runable方法,重载run方法,在窗体中实现设置范围内的数字变换,通过两个按钮来实现不同的操作. import java.awt.BorderLayout; import ...

  4. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  5. Android 兼容 Java 8 语法特性的原理分析

    本文主要阐述了Lambda表达式及其底层实现(invokedynamic指令)的原理.Android第三方插件RetroLambda对其的支持过程.Android官方最新的dex编译器D8对其的编译支 ...

  6. java8 lambda map排序_Android兼容Java 8语法特性的原理分析

    本文主要阐述了Lambda表达式及其底层实现(invokedynamic指令)的原理.Android第三方插件RetroLambda对其的支持过程.Android官方最新的dex编译器D8对其的编译支 ...

  7. java启动servlet_Java Servlet 运行原理分析

    1 Servlet基本执行过程 Web容器(如Tomcat)判断当前请求是否第一次请求Servlet程序 . 如果是第一次,则Web容器执行以下任务: 加载Servlet类. 实例化Servlet类. ...

  8. java hashset 实现_HashSet实现原理分析(Java源码剖析)

    本文将深入讨论HashSet实现原理的源码细节.在分析源码之前,首先我们需要对HashSet有一个基本的理解. HashSet只存储不同的值,set中是不会出现重复值的. HashSet和HashMa ...

  9. Java 随机数产生 Random和Math 解决

    ઇଓ 欢迎来阅读子豪的博客(Java语法篇) ☾ ⋆有什么宝贵的意见或建议可以在留言区留言 ღღ欢迎 素质三连 点赞 关注 收藏 ❣ฅ码云仓库:补集王子 (YZH_skr) - Gitee.com 目 ...

最新文章

  1. Android游戏开发指南lt;一gt;背景地图
  2. 【NLP】 理解NLP中网红特征抽取器Tranformer
  3. 2021牛客多校1 - Hash Function(思维+FFT)
  4. mysql内连接查询原理_MySQL全面瓦解12:连接查询的原理和应用
  5. 别人的电子书,你的电子书,都在bookdown
  6. http://java.sun.com/jsp/jstl/core cannot be resolved(含有jstl1.2jar包网盘)
  7. ListView使用BaseAdapter与ListView的优化
  8. 网站后台输入密码错误
  9. 【OpenCV入门指南】第六篇 轮廓检测 下
  10. 学校教材管理系统html,学校教材管理系统
  11. execute immediate使用方法
  12. 红蜻蜓抓图软件测试简历,红蜻蜓抓图精灵
  13. antv图例出现分页_自定义图例组件
  14. java工具:通过文件头的魔数判断文件类型
  15. 梅赛德斯-奔驰集结全球、中国首秀和上市车型亮相2023上海车展
  16. python小技巧:求32位二进制负数的补码,附剑指offer中的应用
  17. 捞王二闯IPO,谁是“火锅第三股“?||Review
  18. Python调用百度地图api路径查询
  19. web 开发入门(1)
  20. 在阿里的一年,让我的技术思维有了翻天覆地的变化

热门文章

  1. 大数据集群失联问题解决方案
  2. CodeForces 405C Unusual Product
  3. O2O、C2C、B2B、B2C
  4. 海力士芯片 HY57V561620FTP-H 的内存容量问题
  5. Redis设置有效时间
  6. 【U8+】总账期初余额开账按钮是灰色的
  7. live555直播h264视频流
  8. 牛客网 2018年全国多校算法寒假训练营练习比赛(第二场) 题解
  9. ESP8266EX芯片文档
  10. ISCC ISCC客服一号冲冲冲(二)