不安全随机数

举报
object 发表于 2024/10/16 15:20:57 2024/10/16
【摘要】 一、描述不安全随机数也称伪随机数,指可被猜测的随机数,不具有随机性。举例:一副崭新的扑克牌,打开后进行任意洗牌,并按照洗好的顺序给每个人进行发牌,如果眼力和记忆力足够好,则理论上完全可以根据自己的牌,计算出其他人的牌。二、危害对于认证鉴权场景,重要的业务场景,加密数据场景等,如果使用了不安全随机数,则一切都将变得透明,可预测。则认证、加密等都将变得不可信。业务举例1:想象一下,你有一张礼品兑...

一、描述

不安全随机数也称伪随机数,指可被猜测的随机数,不具有随机性。

举例:一副崭新的扑克牌,打开后进行任意洗牌,并按照洗好的顺序给每个人进行发牌,如果眼力和记忆力足够好,则理论上完全可以根据自己的牌,计算出其他人的牌。

二、危害

对于认证鉴权场景,重要的业务场景,加密数据场景等,如果使用了不安全随机数,则一切都将变得透明,可预测。则认证、加密等都将变得不可信。

业务举例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的组合,或者使用更复杂的随机数生成算法。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。