Java 里提供了一些用于生成随机数的工具类,这里分析一下其实现原理,以及他们之间的区别、使用场景。
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.)
* 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.
* 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.
* 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
翻译一下,主要有以下几点:
-
Random 类使用线性同余法 linear congruential formula 来生成伪随机数。
-
两个 Random 实例,如果使用相同的种子 seed,那他们产生的随机数序列也是一样的。
-
Random 是线程安全的,你的程序如果对性能要求比较高的话,推荐使用 ThreadLocalRandom。
-
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 {
this.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;
介绍 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.
主要有以下几点:
- 该类提供了能满足加密要求的强随机数生成器。
- 传递给 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) {
prng = "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) {
throw 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;
protected 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)有关。
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);
在 Linux 操作系统中,有一个特殊的设备文件 /dev/random,可以用作随机数发生器或伪随机数发生器。
在读取时,/dev/random 设备会返回小于熵池噪声总数的随机字节。/dev/random 可生成高随机性的公钥或一次性密码本。若熵池空了,对/dev/random的读操作将会被阻塞,直到从别的设备中收集到了足够的环境噪声为止。
当然你也可以设置成不堵塞,当你在 open 的时候设置参数 O_NONBLOCK, 但是当你read 的时候,如果熵池空了,会返回 -1。
/dev/random 的一个副本是 /dev/urandom (“unlocked”,非阻塞的随机数发生器),它会重复使用熵池中的数据以产生伪随机数据。这表示对/dev/urandom的读取操作不会产生阻塞,但其输出的熵可能小于 /dev/random 的。它可以作为生成较低强度密码的伪随机数生成器,不建议用于生成高强度长期密码。
- 随机算法线性同余法的理解
文章目录java.util.Randomjava.Security.SecureRandom/dev/random 与 /dev/urandom资料Java 里提供了一些用于生成随机数的工具类,这里分析一下其实现原理,以及他们之间的区别、使用场景。java.util.RandomRandom 是比较常用的随机数生成类,它的基本信息在类的注释里都写到了,下面是 JDK8 里该类的注释:/**...
一个非常简单的应用程序,将提供任意两个开始和结束数字之间的随机数。
非常适合模拟骰子或 rng,或者在任何其他需要随机数的时候!
使用 Java 的 Random 类( )和 nextInt(int) 方法来生成数字,如果您对什么感兴趣该应用程序用于生成这些数字。 每次按下按钮时都会生成一个新的 Random 类,因此不会使用两次相同的种子。
泰勒·霍兰德 -
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http:
byte[] salt = new byte[128];
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed(System.currentTimeMillis()); //使用系统时间作为种子
secureRandom.nextBytes(salt);
此处指定...
一、Random
1、生成伪随机数(流),使用48位种子,使用线性同余公式进行修改。可以通过构造器传入初始seed,或者通过setSeed重置(同步);默认seed生成主导变量为系统时间的纳秒数。
2、如果两个(多个)不同的Random实例,使用相同的seed,按照相同的顺序调用相同方法,那么它们得到的数字序列也是相同的。这种设计策略,既有优点也有缺点,优点是“相同seed”生...
centos7.2 启动卡在部署 manager 上 -D
java.
security.egd=file:/dev/./u
random
将$
JAVA_HOME/jre/lib/
security/
Java.
security内,将
secure
random.source的内容改为file:/dev/./u
random即可
Linux或者部分unix系统提供
随机数设备是/dev/
random 和/dev/u
random ,两个有区别,u
random安全性没有
random高,但
random需要时间间隔生成
随机数。
Java生成随机数
一、使用math方法,Math.random()随机生成一个double类型[0,1)
int num = (int)(Math.random()*99);
//生成1~99的随机数
二、使用Random方法生成随机数
public static void testRandom() {
Random random = new Random();
random.setSeed(100L);
for(int i = 0 ; i <
Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。
相同种子数的Random对象,相同次数生成的随机数字是完全相同的。所以,两个种子数相同的Random对象,生成的随机数字完全相同。所以在需要频繁生成随机数,或者安全要求较高的时候,不要使用Ran...
Java中可以使用Math.random()方法生成随机数。该方法返回一个0到1之间的随机浮点数,包括0但不包括1。如果需要生成指定范围内的随机整数,可以使用以下代码:
int randomNum = (int)(Math.random() * (max - min + 1)) + min;
其中,max和min分别为指定范围的最大值和最小值。