不安全随机数
一、描述
不安全随机数也称伪随机数,指可被猜测的随机数,不具有随机性。
举例:一副崭新的扑克牌,打开后进行任意洗牌,并按照洗好的顺序给每个人进行发牌,如果眼力和记忆力足够好,则理论上完全可以根据自己的牌,计算出其他人的牌。
二、危害
对于认证鉴权场景,重要的业务场景,加密数据场景等,如果使用了不安全随机数,则一切都将变得透明,可预测。则认证、加密等都将变得不可信。
业务举例1:想象一下,你有一张礼品兑换卡,但是礼品兑换卡的兑换码使用的不安全随机数,基于某种算法,完全可根据自己兑换码猜测其他人的兑换卡码,则你完全可以任意兑换所有的礼品卡,给业务带来巨大的损失。
业务举例2:常规情况下,B/S应用都是使用的sessionId进行维护用户登录状态,但是如果sessionId可被预测,则系统所有的用户信息都将变得不安全,完全可以根据自己的或者已失效的sessionId找到已登录或后续即将登录的sessionId,例如找到超管的账号,则系统所有的信息都将泄露,给用户带来巨大的损失,危害极大。
三、代码演示
java不安全随机数random类破解:
第一步,java生成多个随机数
public static void main(String[] args) {
Random random = new Random();
System.out.println(random.nextInt()); // 输出1821750562 每次输出不同
System.out.println(random.nextInt()); // 输出475831025 每次输出不同
System.out.println(random.nextInt()); // 输出988004156 每次输出不同
}
第二步,将第一个随机数和第二个随机数填写到解密工具中,查看解密工具生成的第三个随机数是否与代码相同
解密工具代码如下
# coding=utf-8
a = 0x5DEECE66D
b = 0xB
mask = (1 << 48) - 1
def move_num(x):
y = -x
y ^= 2 ** 32 - 1
y += 1
return y
def find_seed(num1, num2):
if num1 < 0:
num1 = move_num(num1)
if num2 < 0:
num2 = move_num(num2)
seed = num1 << 16
for i in range(2 ** 16):
if ((a * seed + b) & mask) >> 16 == num2:
return seed
seed += 1
def get_next_val(seed):
x = seed >> 16
if '{:032b}'.format(x).startswith('1'):
x ^= 2 ** 32 - 1
x += 1
return -x
return x
if __name__ == '__main__':
x1 = 1821750562
x2 = 475831025
seed1 = find_seed(x1, x2)
seed2 = (a * seed1 + b) & mask
seed3 = (a * seed2 + b) & mask
x3 = get_next_val(seed3)
print(x3) // 输出988004156 与预期值一致
上诉代码可以看出,综上所述,可以看出伪随机数是可以被预测的,不安全的。
总结:上诉工具代码总共有两步,第一步是找种子,第二步是计算下一个随机值。 其中种子就是起点值,其它值是根据种子值进行变化得来。对比扑克牌的案例,最开始的牌(牌序)就是种子, 洗牌就是find_seed的逆操作,猜测别人的牌就是get_next_val。
源码解析:
random类两个重要的函数,先看nextInt()。进入后,核心代码就是如下,逻辑也很简单,就是根据旧种子经过一系列运算后,得到新种子,然后进行位运算得到下一个值。其中的几个参与计算的值也是固定值。
第二个,就是new Random()函数本身,核心代码如下,作用是生成初始种子值,后续变化,就根据这个种子进行变化。
同时这种一个重载函数,如果传入固定值,也可以证明,种子的变化是有规律的。两者都会输出-1155484576值,证明结果一致
遗留小问,各个小伙伴自行探索:就是new Random()是当种子固定的时候,后续值才可以猜测,那么我每次都new Random(),不固定seed值,是否就是安全的呢?
答案附末尾
四、案例
本来是不列举案例的,一般未使用SecureRandom.getInstanceStrong()都是不安全的,但是后面学习到了一种不易排查的案例,所以补充一下,给大家提个醒,也算是一个额外的知识点。
案例:SSLContext未使用安全随机数
SSLContext sslContext = new SSLContextBuilder().setProtocol("TLSv1.2").loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
解决方案:
SSLContext sslContext = new SSLContextBuilder().setSecureRandom(SecureRandom.getInstanceStrong()).setProtocol("TLSv1.2").loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
五、解决方案
可参考之前的文档。获取安全随机数
六、排查方法
主要还是依靠人工代码检视的方法来进行排查。依据java中我们常使用到的类、函数、进行定位。
代码搜索random,逐一排查所有涉及的代码。 查看是否涉及认证鉴权、重要业务场景、加密等情况。如果涉及,则都应使用安全随机算法进行获取安全随机数。
对于类似sessionId、csrfToken等框架自动生成的,则需要参考源码,但一般只要对应的框架依赖版本是最新的,非久远的历史老版本,一般来说都是安全随机的。
列举几种常见的不安全算法:
- java.util.random
- /dev/urandom
- math.random
- UUID.randomUUID()
- os.urandom
- SHA1PRNG
- System.currentTimeMillis()等
七、总结
对于认证鉴权、重要业务场景、加解密等场景禁止random()、uuid等伪随机数,优先使用SecureRandom.getInstanceStrong()的方式获取安全随机数。对于框架生成的重要数据、认证凭据等需确保依赖开源可信、版本为可信的版本。
补充:除了上诉两种情况,还有一种情况是不易被发觉的,这种就没法排查,只能依靠经验了。 就是某些函数,需要使用的时候,开启安全配置开关,或者新建安全随机函数,否则默认是不安全的。例如sslcontext,这种确实不易被发掘,只能写代码的时候多看一下参数,或者依靠经验了。 补充一个案例在上面。
上述遗留小问答案:即使每次都new Random(),非固定种子,也是不安全的。 因为new Random()的种子初始化是基于时间生成的,基于时间的随机数都是不安全的, 具体可参考new Random的代码。其中seedUniquifier()为常量,System.nanoTime()是一个native函数,无法获取内部,作用类似于System.currentTimeMillis(),但是返回值与System.currentTimeMillis(),完全不同。网上对于函数作用也是众说纷纭,我觉得比较靠谱的说法是这个时间是基于某个数字算起的,不同于currentTimeMillis从1970.1.1算起,他是一个不确定的值,可能是机器,cpu等一系列综合的一个值,但确实是固定的。
从System.nanoTime()的文档注释中有这么一句话:The value returned represents nanoseconds since some fixed but arbitrary <i>origin</i> time (perhaps in the future, so values may be negative). 大概意思就是 返回值是经由某个固定的值,但这个固定值可以是任意值计算得来的纳秒时间。 通过多次获取System.nanoTime()也可以看出来是类似时间的时间戳,且前几位是保持不变的。
总结:new Random()是不安全的,即使不固定种子。
AI问答
不确定,找个ai咨询看看,感觉是有少许错误,不过一些观点也是可以作为参考意见,回答记录如下:
AI问:System.nanoTime() 是否能作为随机数种子
AI答:System.nanoTime() 返回的是当前的时间戳,单位是纳秒。这个值会随着时间的推移而增加,但是并不能保证其唯一性,因为两个不同的操作可能会在同一纳秒内发生。
在生成随机数时,一个好的种子应该具有高度的唯一性和不可预测性。虽然 System.nanoTime() 可以提供一定程度的唯一性,但是因为它是以纳秒为单位,所以在同一纳秒内,不同的调用可能会得到相同的结果。此外,如果两个不同的线程在同一纳秒内调用 System.nanoTime(),它们可能会得到相同的结果,这可能会导致生成的随机数序列相同。
因此,虽然 System.nanoTime() 可以作为随机数种子,但是它可能不是最好的选择。如果你需要一个更好的随机数种子,你可能需要考虑使用其他的方法,比如使用当前时间戳和当前线程的ID的组合,或者使用更复杂的随机数生成算法。
- 点赞
- 收藏
- 关注作者
评论(0)