Java Random可破解,随机数不再随机,更不安全

张开发
2026/4/3 15:55:25 15 分钟阅读
Java Random可破解,随机数不再随机,更不安全
Java Random 随机数生成不安全如果同时泄漏第一个和第二个随机数那么后面的随机数序列可以被破解。Java Random类使用线性同余生成器Linear Congruential Generator算法来生成伪随机数。所谓伪随机数是指如果我们使用相同的种子seed来生成随机数序列那么得到的结果将是一样的。Random种子相同随机序列则相同举个例子下面的代码每次生成的随机数都是相同的Random random new Random(0); int cnt 10; for (int i 0; i cnt; i) { System.out.println(random.nextLong()); }这意味着当我们设置种子为 0 时每次运行代码得到的随机数序列将是相同的。无论是在任何时间还是在任何设备上以下代码生成的随机数始终保持一致。这也意味着一旦黑客获得了你的种子“seed”他们可以预测出你所生成的所有随机数。要想生成不同的随机数序列我们需要使用不同的种子。幸好Java提供的Random类并没有默认将种子值 0 作为随机数的初始值。相反它采用了一种更为复杂的方式使用动态的随机值作为默认种子。它通过将seedUniquifier()与System.nanoTime()进行异或运算将系统当前纳秒时间和初始种子值进行结合。通过使用纳秒值计算种子可以确保在不同时间构建的Random对象会得到不同的种子值。这样就能够保证生成的随机数具有更高的独立性和随机性。初始种子值如何生成呢private static final AtomicLong seedUniquifier new AtomicLong(8682522807148012L); private static long seedUniquifier() { // LEcuyer, Tables of Linear Congruential Generators of // Different Sizes and Good Lattice Structure, 1999 for (;;) { long current seedUniquifier.get(); long next current * 181783497276652981L; if (seedUniquifier.compareAndSet(current, next)) return next; } }这段代码使用AtomicLong计算种子很明显是为了保证多线程场景下创建多个Random对象时产生不同的种子值。在seedUniquifier方法中使用了AtomicLong.compareAndSet方法来保证每次执行该方法时生成不同的初始种子值。通过这种方法我们可以确保在多线程应用中当出现种子冲突时我们可以通过CAS操作Compare and Swap进行重试以确保每个线程创建的Random对象的初始种子值是不同的。在代码中通过将不同的初始种子值与当前系统时间的纳秒进行异或运算可以保证每次Random执行都会得到不同的种子。那么这是否能保证种子不会被黑客猜到呢Seed可以被推测无论使用何种随机种子、进行何种精心计算和保护都存在可能被推测出的风险。攻击者将根据观察到的输出值计算出种子。在java.util.Random的情况下这比248时间要短得多。怀疑者可以尝试进行一次实验其中展示了仅通过观察两个输出值就可以在大约216时间内预测Random输出的情况。在现代计算机上预测给定时刻的随机数输出的时间甚至不到一秒钟。# Java Random安全性分析本文作者展示了根据两个随机数 预测第三个随机数的算法强烈建议大家试试这段代码如果你们家的随机数用于安全性较高的场景例如兑换码、Token生成等一定要及时更换****private static final long multiplier 0x5DEECE66DL; private static final long addend 0xBL; private static final long mask (1L 48) - 1; Test public void test6() { int a -117510532; int b -1347210525; int c 2076362838;//待预测值 long oldseed ((long) a 16); for (int i 0; i 0xffff; i) {//暴力破解种子 long nextseed (oldseed * multiplier addend) mask; if ((int) (nextseed 16) b) { oldseed nextseed; break; } oldseed 1; } int next_int (int) (((oldseed * multiplier addend) mask) 16); System.out.println(next_int c); }对于nextLong和nextDouble方法只需要知道任意一个随机数即可预测后续随机数而对于nextInt和nextFloat如果同时泄漏第一个和第二个随机数那么后面的随机数也都是可预测的。Random 生成的随机数只有48位这意味着在随机情况下通过有限次数的尝试使用当今先进的CPU就可能在有限时间内破解。对于一些服务来说如果很长时间不重新启动也就意味着种子值(seed)很长时间不更新这样预测种子值将变得更加容易。然而有人可能会质疑预测种子值和推测随机值有什么问题呢在大多数情况下比如随机将请求路由到一台机器或随机选中一个userId等场景即使种子值被猜测到也不会有太大的危险。但是在某些场景下比如使用随机数生成密码、兑换码、推广码、各种Token等如果这些随机数的应用被攻破可能会带来难以估量的损失。例如如果成功推测出兑换码码池中的随机数那么生成的兑换码就不再安全可靠比如兑换京东购物卡。那么有什么方法可以安全地使用随机数呢SecureRandom 生成安全的随机数Java提供了SecureRandom随机数生成类可以安全的生成随机数。SecureRandom 相比 Random有什么优势呢Random 最多生成48位随机值但是SecureRandom最多可生成128位随机值。对于使用Random的情况需要约248次尝试才能破解它但是由于今天先进的CPU实际上可能很快就能破解。而对于SecureRandom则需要约2128次尝试才能破解即使使用今天先进的计算机也需要花费多年的时间。所以SecureRandom提供了更高的安全性。SecureRandom 不使用固定种子值。而是从操作系统/dev/random 随机数文件中不断获取新的种子值。操作系统会收集各种随机数据例如按键之间的间隔等。这些随机数据会被存储在文件中对于linux和solaris系统来说常见的文件路径是/dev/random和/dev/urandom。将这些随机数据作为种子用于生成随机数或执行其他加密或随机化操作SecureRandom 它会从/dev/random文件中获取随机种子值每次调用nextBytes()都会获取不同的随机种子值。通过这种方式即使攻击者观察输出也无法推断出任何信息。因为随机种子一直在变化除非他能够控制/dev/random文件的内容这是非常不可能的SecureRandom 几种策略SHA1PRNGSHA1PRNG是一种伪随机数生成器算法在Java SecureRandom中它被作为Windows下默认的随机数生成算法。该算法基于SHA-1算法但通过添加额外的步骤来提高随机性。SHA1PRNG算法使用一个种子来初始化随机数生成器。SHA1PRNG算法使用SHA-1算法来计算一个哈希值这个哈希值会被用来产生随机数。在SHA1PRNG算法中使用的seed是在系统启动时就指定的。NativePRNGBlockingJava SecureRandom中NativePRNG 算法是Linux 下默认的随机数生成算法。NativePRNGBlocking 初始播种时使用 /dev/random 中的 20 个字节初始化内部 SHA1PRNG 实例当调用 nextBytes()、nextInt() 等使用内部 SHA1PRNG 实例的输出和从 /dev/random 读取的数据的进行 异或NativePRNGBlocking每次计算随机数需要从/dev/random文件中获取数值。当/dev/random的随机数不足时NativePRNGBlocking将会被阻塞。在桌面应用程序中/dev/random文件很少会受到阻塞因为它可以收集用户的鼠标、点击等事件。然而在Web程序中由于并发度较高生成/dev/random数据可能会出现不足的情况。例如有人使用NativePRNGBlocking算法在线上环境服务启动时一直被阻塞。就是因为/dev/random数据较少NativePRNGBlocking 初始化被阻塞。NativePRNGNonBlocking为了避免获取随机数被阻塞NativePRNGNonBlocking选择从/dev/urandom中获取随机数。在/dev/urandom和/dev/random之间有一些区别。/dev/random通过收集系统上的环境噪声如硬件噪声、磁盘活动等来生成随机数。它只在系统上具有足够的环境噪声时才能生成高质量的随机数。而/dev/urandom是一个伪随机数生成器设备文件它使用内部熵池持续生成随机数不管系统上的环境噪声有多少。因此/dev/urandom生成随机数的速度比/dev/random快得多。总结一下/dev/urandom生成的随机数质量稍差但是能稳定输出。而/dev/random生成的随机数质量较高但是在系统噪音较少时生成随机数据较慢可能会阻塞部分应用。因此建议大家一般情况下使用NativePRNGNonBlocking来读取/dev/urandom这样可以换取稳定的随机数据输出虽然牺牲了一些随机数的质量。使用阻塞算法可能导致线上问题 详情参考 使用NativePRNGBlocking 生产环境被阻塞如何使用 SecureRandomSecureRandom 和 Random使用方式类似生成对象时指定对应的策略即可。SecureRandom secureRandom SecureRandom.getInstance(SHA1PRNG); SecureRandom secureRandom SecureRandom.getInstance(NativePRNGBlocking); SecureRandom secureRandom SecureRandom.getInstance(NativePRNGNonBlocking); secureRandom.nextLong();SecureRandom并不是线程安全的可以使用synchronized关键字同步或者 使用ThreadLocal 为每个线程保存一个 SecureRandom实例。各种随机策略性能对比在单线程环境下循环100W次生成随机数。Random算法表现出最强的性能仅需10毫秒即可完成100W次循环。相比之下其他三种SecureRandom算法的耗时是Random的10倍以上。总结在安全性要求较高的场景中使用 Random 生成的随机数是不安全的。如果获得了其中两个随机数的情况下后续的随机序列可以被破解。为了提高安全性需要在一些场景中使用SecureRandom来生成随机数例如在生成随机密码、兑换码和推广码等场景中。SecureRandom算法使用系统随机文件/dev/random和/dev/urandom来生成随机数。由于每次生成的随机数都会与系统随机文件进行异或操作所以随机种子一直在变化使得随机数无法被暴力破解。在Web系统中使用NativePRNGNonBlocking非阻塞随机算法更加适合因为它能提供更高的性能。相比其他三种算法Random 算法具有最强的性能其他三种算法的耗时是 Random 算法的10倍以上。最后安全性较高的业务场景生成随机数时建议使用 SecureRandom不要再用RandomSecureRandom secureRandom SecureRandom.getInstance(NativePRNGNonBlocking); secureRandom.nextLong();最后分享下五阳的浏览器AI插件没想到火了累计下载 1000同时提问豆包 DeepSeek GPT Gemini等10大 AI 平台还支持查询必应 百度 谷歌三大搜索引擎极大提升资料检索和学习效率领取链接

更多文章