It’s been a whole year since Allan Cecil and I, Dan Petro, gave our presentation at DEF CON 29 detailing the systemic problem of insecure use of random number generators (RNGs) in the Internet of Things (IoT) industry. This makes for a great opportunity to check back in and see how the ecosystem has adapted since then.
If you’re not already familiar with our original work, you’ll probably want to check that out first. But the TL;DR is that IoT devices across the board systemically chose random numbers for use in security contexts like crypto keys in a way that could be predicted, even when using a dedicated hardware RNG peripheral.
Note: We’ll try to keep this article updated when updates happen. Last updated: August 24, 2022.
Our top-level recommendation from last year was that the best way to ensure secure randomness is by using a cryptographically secure pseudorandom number generator (CSPRNG) subsystem, and this remains true today. What this does for you as a developer is provide a nice little API that never blocks and always produces securely random numbers. No fusses with quirky hardware, no inconsistent results. Just call the API and you’re set!
These can be quite complex to implement, however, so you wouldn’t want to do that on your own as an IoT device developer. The place you are most likely to find them available for use is in one of the many emerging IoT operating systems.
Below are each of the major IoT operating systems available today and where they currently stand in terms of implementing a centralized CSPRNG subsystem:
Operating System | CSPRNG Subsystem? | Notes |
Contiki-NG | No | |
RIOT-OS | No | Coming soon, in beta |
FreeRTOS | No | Exists in some demos, not centralized |
Mbed OS | Yes | |
Keil | Yes |
This is good news! Each of the major IoT Operating systems has either implemented a CSPRNG subsystem or is on its way towards it.
If you’re starting a brand new IoT product from scratch, we’d highly recommend using one of the IoT operating systems mentioned above, as they can provide the necessary scaffolding to help with both ordinary development and security. However, if you have an existing project that cannot switch, then you might still be able to use a CSPRNG subsystem without a significant engineering lift.
The Mbed TLS project has built an excellent implementation of a CSPRNG for you that is available for use on most platforms. The API for mbedtls_ctr_drbg_random will accumulate entropy from a variety of sources, including hardware RNG peripherals, and smooth it all out into a high-quality stream of bits from a CSPRNG. In fact, this API is what is used under the hood by most of the IoT operating systems mentioned above, so you’ll be in good company.
So, if you don’t have an IoT Operating System to lean on, use mbedtls_ctr_drbg_random.
If you’re developing a brand new device from scratch that performs security-relevant operations, we’d highly recommend selecting a chip that has a dedicated RNG peripheral—make sure to look at your chip’s datasheet! However, not all chips have this feature and your project may be too far along to change now. If this is your case, what options do you have?
Luckily, there are some strategies to generate entropy even on machines that don’t contain a dedicated hardware RNG. We’ve collected a list of some of them below, along with their relative strength. Naturally, this cannot possibly be an exhaustive list and the strength estimate comes with many caveats:
Requirements: Each of these techniques will require your chip/board to have some feature.
Quality: How much entropy you get when an RNG function call returns. Due to the small sample sizes involved, this is not a rigorously empirical value. Instead, it encapsulates how many statistical tests the numbers fail and how bad they do. This may vary wildly depending on any number of factors.
Quantity: How many bits you can pull from this source of entropy. Some may have good quality but may not always be available.
1) Reading voltage from a floating pin
What is the voltage of a pin that’s not connected to anything? In practice, it’s pretty random. It will depend greatly on ambient RF noise, what’s happening on nearby pins, and who knows what else. And it’s that noise that you can use as entropy! All you need to do is to read the voltage of the pin as an analog value and then use the least-significant bits.
Requirements: Common — Unused GPIO pin
Quality: Mediocre — This greatly depends on the ambient radio interference around the floating pin. When I tested this with an Arduino Uno, it would sometimes be locked in at a specific voltage and not change at all, depending on where the board was sitting on my desk! That said, there might not be a good way for attackers to know which value it’s stuck at, even if it were. So, there’s definitely some value here.
Quantity: Medium — If you read too quickly from the pin, you’re going to start getting the same data. Even when there’s radio noise, that noise takes time to change significantly. It might be possible to get 128 bits of seed entropy out of this method but it could take at least several seconds to ensure you did.
2) SRAM-based initialization
When your machine first boots up, the SRAM will be in an initial condition that is pretty random, although that comes with a number of caveats. The data is stored by a loop of back-to-back inverters. There’s a Q side and a "not Q" side when voltage is first applied to the system on initialization, there’s no reason for either one to be preferred. Thus, some of the cells will boot up at 0 and others at 1. So, to get entropy out of the system, all you have to do is to read from the SRAM right when the system boots up, before writing to it.
That’s how it works in theory, anyway. In reality, variations in manufacturing can very well make it so that the high or low side is preferred over the other. The individual bits are also likely to be correlated with one another. In other words, when you use this method, you’re either going to get great entropy or none at all! And there isn’t a good way to know which it will be without testing it on your system.
Furthermore, you need to make sure that the memory isn’t written to by some other code before you read from it! This can be tricky to implement as it must be one of the very first things to run and there isn’t a good way to know if some other routine wrote first to the SRAM you’re trying to read.
Note: There’s a whole paper written about this exact technique. It’s a fascinating read!
Requirements: Fairly universal — SRAM
Quality: Strong but temperamental — There’s some strong entropy here but test it on your system first to make sure it works at all. It might not.
Quantity: Plentiful but one time only — This is a weird one. There’s probably enough SRAM to initialize a CSPRNG in your system but you can only do this once on bootup. After that, the entropy is gone.
3) Radio I/Q data
Much like entropy method number one, you can use electromagnetic noise to generate entropy. Only this time, you use I/Q data from an applicable, dedicated radio peripheral rather than a floating pin. Radio data relying on minute details of analog values in the real world is a decent entropy strategy, although you may run into the problem of needing entropy before the radio hardware has fully initialized.
Requirements: Common — Relevant RF radio hardware
Quality: Medium — When using these kinds of analog values, you’re best off using just the least-significant bits. But the quality you get from those is rather good.
Quantity: Medium — You have to be careful to not poll the radio noise too quickly or you’ll start getting identical or correlated values. Combined with the fact that you can only use LSBs, the total amount of entropy you’re able to pull is limited. But it still is probably enough to get a strong initialization, assuming you can wait a few seconds after booting before doing any security-relevant operations.
4) Fine-grain time from real-time clock
If you have the ability to get the current time in nanoseconds, then the least-significant bits of that timestamp can get you some entropy. The major caveat to this technique is that it can be dangerous to run a timestamp collection in a loop, which might cause the values to correlate with one another. This is especially true on microcontrollers without an operating system to preempt execution. If you sprinkle these timestamps in at arbitrary times, however, such as when network events or human interactions occur, then you can be reasonably sure that they’re not correlated.
Requirements: Very Common — Real-time clock
Quality: Good — As long as you’re not running the timestamps in a tight loop, the LSBs of each timestamp will have some good entropy!
Quantity: Low — The inability to deliberately call the clock in a loop is a big hindrance. You’re best off calling the real-time clock when some other unpredictable event occurs, such as network events, and sprinkling them in that way.
5) Entropy from an external network source
This might sound crazy at first but it’s a legitimate option. Shortly after booting, reach out to some external source such as random.org or even your own custom API and pull down some entropy from the server. While you can’t be sure of the security to that initial communication, you’re quite sure that the entropy you get will be strong.
Requirements: Common — Internet connectivity
Quality: Ideal — Generated using a CSPRNG on the server-side
Quantity: Good but slow and expensive — Making a remote HTTP call is slow and computationally expensive. You may only do it one time during startup but that would be quite enough to bootstrap the rest of the entropy for the entire system going forward.
6) MAC Address/Device UID/Other static hardware IDs
Most devices come with some sort of globally unique identification number. These might even have significant entropy to them, depending on the ID. In the right context, even though the ID is static, the attacker might not have access to this data and it could thus be useful. For example, if there’s a 64-bit randomly generated and not sequentially assigned ID for a CPU peripheral, then this might thwart an attacker who has no physical access.
Requirements: Fairly Universal — A device with a globally unique ID value, such as a MAC address
Quality: Terrible — These IDs are often not secret. If not printed on the device itself, it may be sent around the network. Let’s be honest, there’s not much to be gained here. But, hey, entropy is additive, so why not?
Quantity: One time only — You can only get entropy out of an ID value one time.
Security is not typically a linearly additive endeavor. If you’re trying to secure a web application against injection flaws, two web application firewalls put together are not that much more secure than just one. But luckily, when it comes to generating entropy for an RNG, we can use multiple terrible sources together to form a good one!
Suppose that you have an entropy source that is terrible. Every time you generate a random number, half the bits are totally predictable. Thus, if you try to create a 128-bit random number, you’re only getting 64 bits of actual entropy. Wow, that’s a bad RNG! It should be totally useless, right? Well, not so fast. Here’s the thing: you can always just run the generator a second time and hash the values together:
Hash(RandomNumberOne) xor Hash(RandomNumberTwo) = strong entropy
And just like that, you’ve gone from a weak entropy source to a strong one!
Thus, even the entropy sources listed as weak above can be useful if you use them properly. Continually add to the entropy pool as additional entropy becomes available and only ever use output from the CSPRNG, never from the raw entropy source itself. Every entropy source adding to the pool is purely additive in terms of security. Even if one turns out to be completely predictable and provides no entropy, it is no different from not using it in the first place! That sure is handy.
There’s no excuse anymore: use a CSPRNG subsystem in your IoT devices. The software support is there and the hardware is available. I hope that the information above can help you make engineering decisions on how to best make this happen and to stop doing IoT RNG.