使用SecureRandom产生随机数采坑记录

简介: 使用SecureRandom产生随机数采坑记录

背景



我们的项目工程里经常在每个函数需要用到 Random 的地方定义一下 Random 变量(如下)


public void doSomethingCommon() {
  Random rand = new Random();  
  ...
}
复制代码


在用 sonar 进行检查时,会发现会有如下告警


Creating a new Random object each time a random value is needed is inefficient and may produce numbers which are not random depending on the JDK. For better efficiency and randomness, create a single Random, then store, and reuse it.


简单地说就是在每个函数都创建一个 Random 效率太低了,而且由于 JDK 版本的不同,可能 Random 产生的随机数不够随机。为了提升性能和随机性,建议定义一个 Random 单例来统一产生随机数, Sonar 建议使用 SecureRandom.getInstanceStrong() 来初始化,如下


private Random rand = SecureRandom.getInstanceStrong();
复制代码


于是我们就将其改成 sonar 建议的形式来生成随机数


问题初现



结果问题来了,下午在我们业务的接口上,第三方反馈接口调用超时,从我们的监控来看,接口执行阻塞,看起来像陷入了某种死循环。



定位问题



复现问题:首先使用了相同的请求参数在预发进行了测试,但令人不解的是,问题无法复现。随后又测试了线上机器,可以稳定的复现问题。这时一脸黑人问号。


打日志:为了定位问题,在代码的关键位置插入日志,经过多次的发布定位到问题,执行到 SecureRandom.getInstanceStrong() 方法后就阻塞了。


原因剖析



SecureRandom.getInstanceStrong() 方法在 linux 环境下使用 /dev/random 生成种子。但是 /dev/random 是一个阻塞数字生成器,如果它没有足够的随机数据提供,它就一直等,这迫使 JVM 等待。键盘和鼠标输入以及磁盘活动可以产生所需的随机性或熵。但在一个缺乏这样的活动服务器,可能会出现问题,当系统的熵池中数量不足时,就会阻塞当前线程。


那么 Linux 中随机数是如何产生的呢 PRNG(Pseudo-Random Number Generator),以下解释摘自 IBM 的文章


Linux 内核采用熵来描述数据的随机性,熵(entropy)是描述系统混乱无序程度的物理量,一个系统的熵越大则说明该系统的有序性越差,即不确定性越大。内核维护了一个熵池用来收集来自设备驱动程序和其它来源的环境噪音。理论上,熵池中的数据是完全随机的,可以实现产生真随机数序列。为跟踪熵池中数据的随机性,内核在将数据加入池的时候将估算数据的随机性,这个过程称作熵估算。熵估算值描述池中包含的随机数位数,其值越大表示池中数据的随机性越好。 内核中随机数发生器 PRNG 为一个字符设备 random,代码实现在 drivers/char/random.c,该设备实现了一系列接口函数用于获取系统环境的噪声数据,并加入熵池。系统环境的噪声数据包括设备两次中断间的间隔,输入设备的操作时间间隔,连续磁盘操作的时间间隔等。 对应的接口包括


void add_device_randomness(const void *buf, unsigned int size);
void add_input_randomness(unsigned int type, unsigned int code,
        unsigned int value);
void add_interrupt_randomness(int irq, int irq_flags);
void add_disk_randomness(struct gendisk *disk);
复制代码


内核提供了 1 个的接口来供其他内核模块使用。


该接口会返回指定字节数的随机数。random 设备了提供了 2 个字符设备供用户态进程使用——/dev/random 和/dev/urandom:


  • /dev/random 适用于对随机数质量要求比较高的请求,在熵池中数据不足时, 读取 dev/random 设备时会返回小于熵池噪声总数的随机字节。/dev/random 可生成高随机性的公钥或一次性密码本。若熵池空了,对/dev/random 的读操作将会被阻塞,直到收集到了足够的环境噪声为止。这样的设计使得/dev/random 是真正的随机数发生器,提供了最大可能的随机数据熵。
  • /dev/urandom,非阻塞的随机数发生器,它会重复使用熵池中的数据以产生伪随机数据。这表示对/dev/urandom 的读取操作不会产生阻塞,但其输出的熵可能小于/dev/random 的。它可以作为生成较低强度密码的伪随机数生成器,对大多数应用来说,随机性是可以接受的。


解决方案



有了以上的的解释,我们就知道解决方案了,使用 /dev/urandom 这种非阻塞的方式来产生随机数即可,在 Java 中我们这样写即可


SecureRandom random = new SecureRandom();
复制代码


new SecureRandom() 使用 /dev/urandom 生成种子,不会产生阻塞。

相关文章
|
2月前
|
人工智能 算法 数据挖掘
魔搭社区携手AFAC2025金融智能创新大赛,共同孵化金融科技新星
8月27日,在上海市科学技术委员会指导下,由北京大学、清华大学、复旦大学、香港大学、蚂蚁集团等近30家海内外顶级院校、头部企业、孵化器等机构联合发起的AFAC2025金融智能创新大赛总决赛路演圆满结束。
220 9
|
设计模式 Java Spring
【Spring源码】WebSocket做推送动作的底层实例是谁
我们都知道WebSocket可以主动推送消息给用户,那做推送动作的底层实例究竟是谁?我们先整体看下整个模块的组织机构。可以看到handleMessage方法定义了每个消息格式采用不同的消息处理方法,而这些方法该类并**没有实现**,而是留给了子类去实现。
192 1
【Spring源码】WebSocket做推送动作的底层实例是谁
|
前端开发 Java Linux
cp: can‘t stat ‘/usr/share/zoneinfo/Asia/Shanghai‘: No such file or directory
cp: can‘t stat ‘/usr/share/zoneinfo/Asia/Shanghai‘: No such file or directory
|
6月前
|
Arthas 监控 Java
Arthas jad(字节码文件反编译成源代码 )
Arthas jad(字节码文件反编译成源代码 )
407 13
|
Java Spring 容器
【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析(上)
【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析(上)
【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析(上)
|
9月前
|
数据采集 存储 Java
Java爬虫获取微店店铺所有商品API接口设计与实现
本文介绍如何使用Java设计并实现一个爬虫程序,以获取微店店铺的所有商品信息。通过HttpClient发送HTTP请求,Jsoup解析HTML页面,提取商品名称、价格、图片链接等数据,并将其存储到本地文件或数据库中。文中详细描述了爬虫的设计思路、代码实现及注意事项,包括反爬虫机制、数据合法性和性能优化。此方法可帮助商家了解竞争对手,为消费者提供更全面的商品比较。
|
12月前
|
弹性计算 Prometheus 运维
一文详解阿里云可观测体系下标签最佳实践
在当今数字化转型加速的时代,企业 IT 系统的复杂度与日俱增,如何高效地管理和监控这些系统成为了一项挑战。阿里云作为全球领先的云计算服务商,提供了一整套全面的可观测性解决方案,覆盖从业务、端侧(小程序、APP、H5 等)、应用、中间件、容器/ECS 等全栈的监控体系,旨在帮助企业构建强大而灵活的可观测性体系。其中,标签(Tag)作为一种核心组织和管理手段,在阿里云可观测体系中扮演着至关重要的角色。本文将深入探讨阿里云可观测系列产品中标签的应用,以及如何运用标签在阿里云可观测产品体系下进行体系化建设并给出相关最佳实践。
1015 202
|
消息中间件 缓存 API
go-zero微服务实战系列(三、API定义和表结构设计)
go-zero微服务实战系列(三、API定义和表结构设计)
|
Python
求助 当前python 3.12.3 怎么解决
在尝试从`modelscope.pipelines`导入`pipeline`时,遇到了导入错误。问题出在`modelscope.msdatasets.utils.hf_datasets_util`模块尝试从`datasets.utils`导入`_datasets_server`,但该名称未找到,可能应导入的是`_dataset_viewer`。这是Python项目中的一个依赖冲突或库更新问题。
|
XML Java 数据库连接
解决org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题
解决org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题
14155 2
解决org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题