基本上,每个带有硬件随机数生成器 (RNG) 的物联网设备都存在一个严重的漏洞,该漏洞无法使设备正确生成随机数,这会破坏任何上游使用者的安全性。
为了进行安全操作,计算机需要通过 RNG 生成机密信息。然后,这些秘密构成了密码学、访问控制、身份验证等的基础。生成这些秘密的确切方式和原因的详细信息因每次使用而异,但规范示例是生成加密密钥:
Alice 和 Bob 尝试使用 RNG 进行私人对话
为了让Alice和Bob秘密通信,他们需要使用 RNG 生成共享秘密。 Eve 起初并不知道这个号码是阻止她泄露通讯机密的唯一原因。所以,无论是用于身份验证的 SSH 密钥还是用于授权的会话令牌,随机数都是计算机安全的基石之一。
但在物联网设备中,这些“随机”选择的数字并不总是像你希望的那样随机。事实上,在许多情况下,设备选择的加密密钥为0或更糟。
截至2021年,大多数新的物联网系统(soc)都有专门的硬件RNG外设,旨在解决这个问题。但不幸的是,事情并没有那么简单。所以,如何使用外围设备就显得至关重要。
调用硬件 RNG时产生的漏洞
当开发人员未能检查错误代码响应时,会发生一个更明显的漏洞,这通常会导致与安全相关的使用所需要的数字不是那么随机。
当物联网设备需要随机数时,它会通过设备的 SDK 或越来越多地通过物联网操作系统调用专用硬件 RNG。当然,函数调用的名称各不相同,但它发生在硬件抽象层 (HAL) 中。这是由设备制造商创建的 API,旨在让你可以更轻松地通过 C 代码与硬件交互,而无需设置和检查设备独有的特定寄存器。 HAL函数看起来像这样:
我们关心的有两个部分:
一个名为 out_number 的输出参数,这是函数放置随机数的地方;它是一个指向无符号 32 位整数的指针。
指定任何漏洞情况的返回值,根据设备的不同,它可以是布尔值或任意数量的枚举漏洞条件。
所以,你可能会问的第一个问题是,“有多少人在野外实际检查这个漏洞代码?”不幸的是,答案是几乎没有人。
例如,只需查看使用 MediaTek 7697 SoC HAL 功能的 GitHub结果:
甚至是 FreeRTOS(一种流行的物联网操作系统)的抽象层(参见此处的 GitHub):
请注意,返回代码普遍没有被检查。尽管这不是这两个示例所独有的。这正是物联网行业的做法,你会在每个SDK和物联网操作系统中发现这种行为。
发生最糟糕的情况
所以设备不会检查 RNG HAL函数的漏洞代码。但它到底有多糟糕呢?这取决于特定的设备。
RNG 外设的 HAL 功能可能会由于多种原因而失败,但迄今为止最常见和可利用的是设备的熵已用完。硬件 RNG 外围设备通过各种方式,例如模拟传感器或 EMF 读数,将熵从设备中提取出来,但不能无限供应。它们每秒只能产生这么多的随机位。如果你在 RNG HAL函数没有提供任何随机数时尝试调用它,它将失败并返回漏洞代码。因此,如果设备试图过快地获取过多的随机数,则调用将开始失败。
但这就是随机数的问题,只有一个是不够的。当设备需要生成新的 2048 位私钥时,作为一个保守的例子,它会循环调用RNG HAL函数。这开始严重影响硬件的计算能力,而在实践中,他们往往做不到。最初的几个调用可能成功,但它们通常很快就会开始导致漏洞。
那么,当HAL函数失效时,它会给你一个随机数字带来什么呢?根据硬件的不同,有以下几种:
部分熵
数字 0
未初始化的内存
那不应该存在
这些都不是很好,但未初始化的内存?这是怎么发生的?记住随机数是一个输出指针。然后考虑下面的伪代码:
random_number 变量被声明并存在于堆栈中,但从未被初始化。如果 HAL函数的行为使得它在发生漏洞时从不覆盖输出变量(这是常见行为),则变量中的值将包含未初始化的 RAM,然后通过网络将其发送给其他人。不要以为这些都是理论上的分析,现在市面上的设备都在使用0或更糟的加密密钥。
Keyfactor在2019年对公开可用的RSA证书进行的一项分析发现,所有172个证书中有1个容易受到已知攻击。研究人员发现,物联网设备中的随机数生成是罪魁祸首之一,但这也只是猜测。
如果你是正在为安全通信生成加密密钥的网络堆栈,你应该如何“处理”漏洞?实际上只有两种选择:
中止,杀死整个进程;
在 HAL函数上旋转循环无限长的时间,直到调用完成,阻止所有其他进程并在进程中使用 100% 的 CPU。
这两种解决方案都是不可接受的。这就是开发人员忽略漏洞条件的原因,替代方案很糟糕,围绕 RNG 硬件的生态系统对他们没有好处。
即使开发人员有充足的时间,情况也没有好到哪里去。有些设备,如STM32,有大量的文档,甚至供应商提供的随机性证明白皮书,但这些都是例外。很少有设备甚至对硬件 RNG 应该如何工作有基本的描述,也很少有设备拥有关于预期操作速度、安全的操作温度范围,和随机的统计证据等基本内容的任何类型的文档。
有趣的是,试图仔细按照 STM32 文档的操作说明,仍然设法创建漏洞处理漏洞响应的代码。当出现漏洞响应时,需要多次尝试和大量代码才能正确阻止对 RNG 和自旋循环的额外调用。即使这样,我们也观察到有问题的结果,这让我们怀疑我们的代码。难怪开发人员在做物联网 RNG。
CSPRNG子系统
伪随机数产生器(Cryptographically Secure Pseudo-Random Number Generator,简称CSPRNG)是一个工具,常用的算法有 MD5 或者 SHA1 等标准,它们可以将不定长的信息变成定长的 128 二进位或者 160 二进位随机数。
CSPRNG 子系统组件
当应用程序在 Linux 服务器上需要加密安全的随机数时,它不会直接从硬件 RNG 读取或调用某些 HAL函数并阻止漏洞代码。它只是通过 /dev/urandom 读取。这是一个加密安全的伪随机数生成器 (CSPRNG) 子系统,可作为 API 提供给应用程序。每个主要操作系统上也有类似的子系统:Windows、iOS、MacOS、Android、BSD,等等。
重要的是,对 /dev/urandom 的调用永远不会失败并且永远不会阻止程序执行。 CSPRNG 子系统可以立即产生无穷无尽的强随机数序列。这直接解决了HAL函数要么阻止程序执行要么失败的问题。
CSPRNG 子系统的另一个关键设计特征是熵池。这是为了从各种来源获取熵,包括硬件 RNG (HWRNG)。由于异或运算的魔力,所有这些单独的弱熵源都可以组合成一个强熵源。这是生成加密安全随机数的正确方法,并且已经成为所有地方的行业标准,除了物联网。
为什么你真的需要一个 CSPRNG 子系统
设计一个完整的 CSPRNG 子系统听起来真的很难,尤其是当你的小工具没有使用这些新的物联网操作系统之一时。也许只需要咬紧牙关,在RNG HAL功能上进行循环就足够了。这样就能得到很好的随机数,对吧?
本节将否定你认为硬件 RNG 完全安全的想法。
易受攻击的参考代码
没有人会完全从零开始编写源代码,尤其是在物联网设备领域。总有一些参考代码或示例文档可供开发人员参考。与硬件的接口对于任何设备来说都是棘手的,更不用说像硬件RNG外围设备这样挑剔的设备了。
当设备的参考代码包含漏洞时,它会向下传播到使用它的每个设备。毕竟,开发人员应该如何知道易受攻击的参考实现和古怪的参考实现之间的区别?下面是一些例子:
使用 HWRNG 传播不安全的 PRNG
像libc rand()这样的prng是非常不安全的,因为它们产生的数字揭示了RNG的内部状态信息。它们适用于与安全性无关的上下文,因为它们快速且易于实现。但使用它们来加密密钥等内容会导致设备安全性的灾难性崩溃,因为所有的数字都是可预测的。
不幸的是,许多支持硬件 RNG 的 SDK 和操作系统在幕后使用不安全的 PRNG。 Nordic Semiconductor 的 nrf52840 SoC 的 Contiki-ng 物联网操作系统通过使用硬件 RNG 传播不安全的 libc rand() 函数来实现这一点:
https://contiki-ng.readthedocs.io/en/release-v4.6/_api/arch_2cpu_2nrf52840_2dev_2random_8c_source.html
使用硬件 RNG 熵播种 libc rand()
将来调用random_rand()调用不安全的libc rand()
需要明确的是,没有一种安全的方法可以使用libc rand()生成安全的值。使用硬件创建种子的事实是无关紧要的,因为攻击者可以使用untwister派生或枚举它。重要的是,当用户调用random_rand()时,输出将来自不安全的libc rand()调用。
你还可以在 MediaTek Arduino 代码中看到相同的易受攻击行为(在此处的 GitHub上):
MediaTek SDK 中不安全的 libc rand() 使用
所以,即使你的设备有一个硬件RNG外设,即使你认为你在使用它,也可能不安全。
使用怪癖
有时,设备的工作方式非常古怪,如果不能正确地解释这些怪癖,可能会导致设备安全性的灾难性崩溃,下面就以LPC 54628为例进行说明。
在测试 LPC 54628 时,我们注意到我们从硬件 RNG 中获得了质量极差的随机数,结果如此糟糕以至于我们怀疑我们的工具可能有问题。事实证明我们是对的。如果你仔细阅读用户手册,你会注意到以下说明:
“随机数生成器产生的随机数的质量(熵)依赖于内部逻辑的初始状态。如果需要使用128位或256位的随机数,建议不要将几个32位的字串接成一个随机数。例如,如果两个128位的字串接在一起,硬件RNG将不会提供2倍128位的熵。”
为了构成一个128位的数,先读取一个32位的随机数,然后读取接下来的32个数,但不使用。读取和使用下一个 32 位数字,依此类推。因此,32位的随机数会在使用的两个32位数字之间跳过。
为了正确使用RNG外设,你应该得到一个随机数,然后抛出后面的32。然后继续循环!在我们的测试代码中,我们只是调用 RNG HAL函数并使用结果,这是一种相当合理的编写代码的方式。有多少人仔细去阅读那个用户手册?
显然,安全依赖于这种行为是不可接受的。
统计分析
事实证明,物联网硬件 RNG 外围设备的原始熵质量差异很大。大多数设备未能通过统计分析测试,这些结果通常取决于单个设备本身,因为产品质量,即使是相同品牌和型号的设备也会产生不同的结果。
MediaTek 7697
在分析熵时,我们测试的内容之一是RNG产生的字节的相对分布。在理想情况下,我们应该期望每个字节的可能性相等,因此字节的分布应该是一条平坦的线。但我们看到的MT7697并非如此:
MediaTek 7697 SoC上从0到255的每个字节频率的直方图
上图是一个直方图,显示了从 MediaTek 7697 SoC 的硬件 RNG 凭经验测量的每个可能字节 (0 -> 255) 的相对频率。请注意锯齿图案:这绝对不是随机的。不应该有任何类型的模式。直接使用它作为你的加密密钥,你感觉安全吗?
Nordic Semiconductor nrf52840
Nordic Semiconductor nrf52840 SoC 的硬件 RNG 表现出 0x000 的重复 12 位模式,每 0x50 个字节出现一次:
在 nrf52840 SoC 中重复 0x000
事实上,这是12个字节,而不是8或16个字节,这很奇怪。对此我们没有一个很好的解释,但这可能取决于黑盒RNG硬件的内部工作方式。
STM32-L432KC
不幸的是,我们没有一个很好的图表来展示这一点,但以下是来自 Dieharder测试套件的结果:
STM32-L432KC SoC 的统计分析结果示例
如你所见,这未能通过顽固的“RGB 最小距离”测试。该测试的作用是使用 RNG 在大网格中随机绘制数字,然后计算任意两点之间的最小距离。这个测试特别擅长识别数字之间的细微关联,比如重复。如果测试失败,可能表明RNG产生的数字不是相互独立的,或者它们以某种方式重复。
虽然我们测试的许多硬件 RNG 看起来都不错,例如 LPC54628和 ESP32,但在这里得出的唯一合理结论是,不应孤立地信任从硬件 RNG 中获取的原始熵。 CSPRNG 子系统提供的密钥拉伸和熵池从根本上是避免执行 IoT RNG 所必需的。
总结
物联网需要一个 CSPRNG 子系统,这个问题不能仅仅通过修改文档和责备用户来解决。如果你要从头开始设计新设备,我们建议你在操作系统中实现 CSPRNG。
RNG代码应该被认为是危险的,就像加密代码一样。不管你有多聪明,永远不要自己编写与RNG硬件接口的代码。你几乎肯定会出错。不要直接从RNG硬件中使用熵。总的来说,硬件 RNG 不适合加密使用。
设备所有者
留意更新并确保在可用时应用它们。这是一个可以通过软件解决的问题,但可能需要一些时间。与此同时,请注意不要过度信任你的 IoT 小工具。对于需要互联网连接的家庭设备,请将它们放置在只能从外部访问的专用网段中,这将有助于遏制传播到网络其他部分的任何违规行为。
物联网设备开发商
如果可能,请选择包含从包括硬件 RNG 在内的各种熵源中传播的 CSPRNG API 的物联网设备。如果没有可用的 CSPRNG 并且你别无选择,请仔细检查你所依赖的库以及你自己的代码,以确保你没有使用从未初始化的内存读取、忽略硬件 RNG 外设寄存器或漏洞的代码条件,或者在没有更多可用熵时无法阻止。
设备制造商/物联网操作系统
在你的 SDK 中弃用和或禁用 RNG HAL函数的任何直接使用。相反,应该包含一个 CSPRNG API,该 API 使用具有适当硬件 RNG 处理的稳健且多样化的熵源进行传播。 Linux内核对dev/urandom的实现可以作为一个很好的参考。
本文翻译自:https://labs.bishopfox.com/tech-blog/youre-doing-iot-rng如若转载,请注明原文地址