使用熵生成数据,或随机数生成,是一个众所周知的难题。许多加密算法和协议都假设随机数据可用。有许多实现,包括/BSD和Linux内核中的dev/random以及GnuTLS或OpenSSL等加密库中的API调用。通过阅读源代码可以了解它们的工作原理。数据的质量取决于实际硬件和可用的熵源——RNG实现本身是确定性的,它只是用一组数据源的假定熵转换数据,然后生成输出流。
在某些情况下,例如在虚拟化环境或小型嵌入式系统上,很难找到足够数量的源。对于你得到的数据中有多少熵,几乎没有任何下限估计。您可以通过使用单独的硬件RNG来改善RNG问题,但这其中存在部署复杂性,从理论上讲,这是一个问题,即您只需将良好的随机数据从一个系统移动到另一个系统即可获得。(还有更多关于硬件RNG的内容,我将留到以后再说。)
出于某些目的,可用的解决方案并没有给我带来足够的信心,因为它的复杂性很高。复杂性往往是安全的敌人。在加密讨论中,我半开玩笑地说,关于我唯一信任的RNG过程,是一个我可以用简单的语言解释并在纸笔帮助下实现自己的过程。通常,我使用滚动常规六面的示例骰子(D6)多次。我最近一直在更详细地思考这个过程,觉得是时候把它写下来了,不管它看起来有多傻。
一个有六个边的模具产生一个介于1和6之间的随机数。直观地说服自己它没有明显的偏见是相对直接的:检查它看起来是否对称,并进行一些试验。通过反复滚动骰子,您可以在时间允许的情况下生成所需的数据量。
我不了解足够的热力学知识,不知道如何估计物理过程的熵,所以我需要求助于直观的论点。很容易假设一个骰子产生2.5比特的熵,因为log_2(6)~=2.584。至少我发现直觉上很容易说服自己,2.5位是一个上限,在我看来,没有比看掷骰子结果更大的熵了。不过,我怀疑大多数骰子都有某种形式的缺陷,这导致了在大量掷骰时可以发现的非常小的偏差。因此,我建议大多数D6的平均熵略低于2.5位。此外,为了建立一个下限,直观地说,似乎很容易相信,如果特定D6的熵接近2位,而不是2.5位,那么通过试卷可以很快地看到这一点。这是假设模具中没有复杂的逻辑和机械来隐藏模式。戴上锡箔帽,考虑一个带有电源和机械装置的模具,它可以决定落在哪个数字上:它可以生成看起来像接缝的随机图案,其中仍然包含0位熵。例如,假设构建一个D6来生成模式4、1、4、2、1、3、5、6、2、3、1、三、6、3、五、6、4……这意味着它生成0位熵(将数字与sqrt(2)的小数进行比较)。其他因素也可能影响输出中的熵量,如果你只是从桌子上方1厘米/1英寸直接放下模具,就可以考虑了。也可能还有其他原因导致掷骰子中的熵更有限,直觉论证有时是完全错误的!以本次讨论为背景,为了简单起见,接下来,我将假设我的D6在每次滚动时产生2.5位的熵。
我们需要计算出需要滚动多少次。我通常需要一个128位的随机数(16字节)。加密算法和协议通常使用power-of-2数据大小。64位熵导致暴力攻击,需要大约2^64次测试,对于许多操作,这在当今的计算能力下是可行的。使用当今的技术,执行2^128操作似乎是不可能的。要使用每卷产生2.5位熵的D6生成128位熵,需要执行ceil(128/2.5)=52卷。
我们还需要设计一种算法,将D6输出转换为结果的128位随机数。虽然从理论上看,让D6输出的每一位都影响128位随机数的每一个位是很好的,但这对于笔和纸来说变得很难做到。更新:这篇博客帖子曾经在这里包含了一个算法,但它显然是错误的(写得太晚了……)所以我删除了它——我需要回来进一步思考这个问题。
那么下一步是什么?取决于您想将随机数据用于什么目的。出于某些目的,例如生成一个高质量的128位AES密钥,我就完成了。钥匙就在那里。要生成高质量的ECC私钥,您需要生成更多的随机性(匹配ECC曲线大小),并执行一些EC操作。不幸的是,要生成高质量的RSA私钥,您需要更多的随机性,在这种情况下,使用此过程生成的强大128位种子来实现PRNG更为合理。后一种方法是通用的解决方案:使用骰子方法生成128位数据,然后播种您选择的CSPRNG以快速获得大量数据。这些步骤有些技术性,并且您会丢失纸和笔属性,但与验证您从RNG实现中获得的高质量熵相比,实现这些部分的代码更容易验证。