From 6aa14a75d432d91b5f8d2db73960da3558499b22 Mon Sep 17 00:00:00 2001 From: Triss Date: Thu, 24 Aug 2023 04:13:04 +0200 Subject: [PATCH] add readme --- README.md | 354 +++++++++++++++++++++++++++++++++++++++++++++++++ img/jitter.png | Bin 0 -> 9917 bytes src/main.c | 2 + 3 files changed, 356 insertions(+) create mode 100644 img/jitter.png diff --git a/README.md b/README.md index 0c804df..8ff84a5 100644 --- a/README.md +++ b/README.md @@ -1 +1,355 @@ # rorand + +Ring oscillator-based TRNG for the RP2040 + +## What is it + +`rorand` provides a true random number generator (TRNG) using only hardware +that's available inside the +[RP2040](https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html) +chip. It can be used either in its 'raw' form, or with an extra +whitening/compression step based on [Ascon](https://ascon.iaik.tugraz.at/)-Xofa. + +## Comparison + +| Name | Throughput | Entropy assurance | Extra components | Whitening | URL | +|:--------------------- |:------------------------ |:------------------------------ |:---------------- |:---------- |:------------- | +| rorand (this work) | 40 kbit/s | passes Dieharder (see below) | none | Ascon-Xofa | you are here! | +| Adafruit Trinkey TRNG | (40 kbit/s comms. limit) | Common Criteria EAL6+ | Infineon Trust M | ? | https://learn.adafruit.com/trinkey-qt2040-true-random-number-generator/overview | +| caprand | ? | ? | one 10nF capacitor | ChaCha20 | https://github.com/mkj/caprand | +| rp2040-entropy | 300 kbit/s? | fails a few dieharder tests | none (ADC noise) | none | https://github.com/hashky/rp2040-entropy | +| rprand | ~10 kbit/s | ? | none (ROSC+DMA) | CRC32 | https://github.com/alastairpatrick/rprand | +| pico-sdk | ? (probably >1Mbit/s) | ? (seed only, RNG is Xorshiro) | none (ROSC seed) | Xorshiro128 | https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_rand/rand.c | +| Cornell ECE4760 | ? | convergence to normal distribution | none (ROSC seed) | newlib/RAND48 | https://people.ece.cornell.edu/land/courses/ece4760/RP2040/C_SDK_random/index_random.html | + +## Requirements + +* Pico C SDK +* System must not run from ROSC, and the system clock must be at least 48 MHz + (80 MHz or higher preferred, 125 MHz or higher ideal). +* SysTick not used by something else + +## How it works + +The RP2040 includes a [ring +oscillator](https://en.wikipedia.org/wiki/Ring_oscillator) (unimaginatively +called ROSC). Such an oscillator is nothing more than a chain of inverters +wired up in a loop. If the number of inverters is odd, the circuit will +oscillate. + +Ring oscillators (just like many other oscillators) exhibit a phenomenon called +*jitter*: the length of the clock period varies ever so slightly from one +period to the next. This stems from various forms of noise (thermal noise, shot +noise, etc.) in the semiconductor, which are ultimately caused by the inherent +randomness of quantum physics. + +The RP2040 ROSC has a register called +"[RANDOMBIT](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-rosc-RANDOMBIT)", +which is basically a way of sampling the current state of the oscillator. On +first sight, this *sounds* like it would be random. However, much of the +perceived 'randomness' would actually stem from +[aliasing](https://en.wikipedia.org/wiki/Aliasing), because the code reading +this register would be undersampling. + +Instead, let's use the jitter phenomenon slightly differently: the phase +(jitter) noise from multiple oscillator cycles accumulates. Thus, the actual +real-world time it takes to complete `N` clock cycles has a larger variance +than the jitter of just one clock cycle. This is visualized in the image below: + +![A diagram of multiple jittery squarewaves overlaid. They all start at the +same time, but their ending time varies a lot (while the first few edges all +happen at mostly the same moment). Image from slides by Adriaan Petermans.](img/jitter.png) + +By using the [COUNT](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#reg-rosc-COUNT) +register of the RP2040 ROSC, we can time how long it takes for it to reach zero +(compared to eg. the ARM SysTick timer running from the CPU clock). We can use +the least-significant bit of this timing difference (counted in CPU cycles) as +the randomness output! + +(As a sidenote: 'real professional' ring oscillator-based TRNGs typically use a +"transient effect ring oscillator" (TERO) design, which is a bit more robust. +Though, it also needs multiple oscillators and counters, and we don't have that +available here. So, the basic RO TRNG it is.) + +Well, almost. We still need to do a crucial final step: [entropy +extraction](https://en.wikipedia.org/wiki/Randomness_extractor). There are two +types of entropy extractors used most often: the parity filter (which XORs the +last N output bits together), and the Von Neumann filter (described in the link). +The Von Neumann filter is able to remove all [statistical +bias](https://en.wikipedia.org/wiki/Bias_(statistics)), but has more +requirements on the probability distribution of the inputs (they must be +"Independent and identically distributed", or IID for short). Meanwhile, the +parity filter has fewer requirements (only independence of inputs), but it +cannot remove all bias in practice (though it converges to this for larger and +larger N). + +For a ring oscillator, one would expect the random bits (generated as described +above) to be IID, so we can use the Von Neumann extractor. (This assumption +seems to hold in practice.) Oh, and we can still XOR this bit with the +RANDOMBIT register, because it's there. + +This is essentially how the "raw" output is generated. `rorand` also provides a +"cooked" output, which feeds the output of the "raw" TRNG into Ascon-Xofa. This +creature is a cryptographic PRNG based on what cryptographers call a "[sponge +construction](https://en.wikipedia.org/wiki/Sponge_function)". Sponge +constructions have all sorts of cool properties (which I won't talk about here +because it'd detract from the article, sadly), but one of them is that you can +build an *extensible-output function* (XOF) from them. XOFs are like hash +functions, but the output length can be made arbitrarily long. This makes it +rather easy to make a cryptographically-secure *reseedable* PRNG. + +Ascon is the finalist for the "lightweight portfolio" of the [CAESAR +competition](https://competitions.cr.yp.to/caesar-submissions.html). It's a +rather new (but promising, and built from solid foundations) cryptographic +cipher, specifically designed for slow embedded devices, such as the RP2040. It +should thus be faster than other options like ChaCha20/BLAKE2 or ARC4Random +(which is now considered insecure). AEGIS and Deoxys-II, the other two CAESAR +finalists for other purposes, are based on the AES round transformation, and +would thus be quite a bit slower. + +## How good is it + +### Entropy + +The randomness obtained from this TRNG seems to pass the +[Dieharder](https://webhome.phy.duke.edu/~rgb/General/dieharder.php) test +suite. The output is given below. + +Note that a few entries are marked as `WEAK`, but those seem to appear and +disappear when I try and retry different runs. This seems to happen due to the +used sample size: one megabyte of random data takes a few minutes to generate, +so much larger samples are quite a bit of a hassle to obtain. Running output +from `/dev/random` with the same sizes also has the `WEAK` warnings pop up and +disappear at random, so it should be fine. + +**WARNING**: However, these `WEAK` warnings seem to pop up more often with +decreasing RP2040 system clock speed (and, slightly, voltage as well). While no +`FAILED` errors have popped up, depending on your needs, only use `rorand` with +a high enough system clock frequency (≥80 MHz preferred, ≥125 MHz ideal). + +Dieharder isn't the most stringent test suite, though. +[TestU01](http://simul.iro.umontreal.ca/testu01/tu01.html)'s BigCrush suite is +much more comprehensive, but requires ***much*** more data (many gigabytes). +Thanks to the limited throughput of a hardware TRNG, using this test isn't very +feasible. Real evaluations such as FIPS 140-2, or Common Criteria EAL4 or +higher, is completely out of my means. + +In conclusion, don't trust someone's life --- neither your own nor someone +else's --- with this. But otherwise, you should be fine. If you need the +assurance (or if you're paranoid), please use a FIPS- or Common +Criteria-certified hardware module, though. + +Dieharder output (raw output, RP2040 running at 250 MHz, core voltage 1.15 V): + +``` +#=============================================================================# + test_name |ntup| tsamples |psamples| p-value |Assessment +#=============================================================================# + diehard_birthdays| 0| 100| 100|0.22538373| PASSED + diehard_operm5| 0| 1000000| 100|0.63794545| PASSED + diehard_rank_32x32| 0| 40000| 100|0.73090642| PASSED + diehard_rank_6x8| 0| 100000| 100|0.99254745| PASSED + diehard_bitstream| 0| 2097152| 100|0.58114397| PASSED + diehard_opso| 0| 2097152| 100|0.18319256| PASSED + diehard_oqso| 0| 2097152| 100|0.93914840| PASSED + diehard_dna| 0| 2097152| 100|0.74871515| PASSED +diehard_count_1s_str| 0| 256000| 100|0.20413208| PASSED +diehard_count_1s_byt| 0| 256000| 100|0.56507750| PASSED + diehard_parking_lot| 0| 12000| 100|0.43004782| PASSED + diehard_2dsphere| 2| 8000| 100|0.68150341| PASSED + diehard_3dsphere| 3| 4000| 100|0.50961304| PASSED + diehard_squeeze| 0| 100000| 100|0.73895828| PASSED + diehard_sums| 0| 100| 100|0.89067279| PASSED + diehard_runs| 0| 100000| 100|0.88549264| PASSED + diehard_runs| 0| 100000| 100|0.17826705| PASSED + diehard_craps| 0| 200000| 100|0.74363795| PASSED + diehard_craps| 0| 200000| 100|0.92809716| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.21033260| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.90826850| PASSED + sts_monobit| 1| 100000| 100|0.66704052| PASSED + sts_runs| 2| 100000| 100|0.82376608| PASSED + sts_serial| 1| 100000| 100|0.40789077| PASSED + sts_serial| 2| 100000| 100|0.11533197| PASSED + sts_serial| 3| 100000| 100|0.57906405| PASSED + sts_serial| 3| 100000| 100|0.88247272| PASSED + sts_serial| 4| 100000| 100|0.35109410| PASSED + sts_serial| 4| 100000| 100|0.06001064| PASSED + sts_serial| 5| 100000| 100|0.24597035| PASSED + sts_serial| 5| 100000| 100|0.42776664| PASSED + sts_serial| 6| 100000| 100|0.34671248| PASSED + sts_serial| 6| 100000| 100|0.63443378| PASSED + sts_serial| 7| 100000| 100|0.34901523| PASSED + sts_serial| 7| 100000| 100|0.87482945| PASSED + sts_serial| 8| 100000| 100|0.83333396| PASSED + sts_serial| 8| 100000| 100|0.98096154| PASSED + sts_serial| 9| 100000| 100|0.18613150| PASSED + sts_serial| 9| 100000| 100|0.98222632| PASSED + sts_serial| 10| 100000| 100|0.06064720| PASSED + sts_serial| 10| 100000| 100|0.53659409| PASSED + sts_serial| 11| 100000| 100|0.48783888| PASSED + sts_serial| 11| 100000| 100|0.44013196| PASSED + sts_serial| 12| 100000| 100|0.89056741| PASSED + sts_serial| 12| 100000| 100|0.55084354| PASSED + sts_serial| 13| 100000| 100|0.55991536| PASSED + sts_serial| 13| 100000| 100|0.79919790| PASSED + sts_serial| 14| 100000| 100|0.99559967| WEAK + sts_serial| 14| 100000| 100|0.60230091| PASSED + sts_serial| 15| 100000| 100|0.91600221| PASSED + sts_serial| 15| 100000| 100|0.29385991| PASSED + sts_serial| 16| 100000| 100|0.80170010| PASSED + sts_serial| 16| 100000| 100|0.89169608| PASSED + rgb_bitdist| 1| 100000| 100|0.25864662| PASSED + rgb_bitdist| 2| 100000| 100|0.41244241| PASSED + rgb_bitdist| 3| 100000| 100|0.80837541| PASSED + rgb_bitdist| 4| 100000| 100|0.93133416| PASSED + rgb_bitdist| 5| 100000| 100|0.70903269| PASSED + rgb_bitdist| 6| 100000| 100|0.29717638| PASSED + rgb_bitdist| 7| 100000| 100|0.71361547| PASSED + rgb_bitdist| 8| 100000| 100|0.03450956| PASSED + rgb_bitdist| 9| 100000| 100|0.14545557| PASSED + rgb_bitdist| 10| 100000| 100|0.90299368| PASSED + rgb_bitdist| 11| 100000| 100|0.53321027| PASSED + rgb_bitdist| 12| 100000| 100|0.65243210| PASSED +rgb_minimum_distance| 2| 10000| 1000|0.29295560| PASSED +rgb_minimum_distance| 3| 10000| 1000|0.28825100| PASSED +rgb_minimum_distance| 4| 10000| 1000|0.84249889| PASSED +rgb_minimum_distance| 5| 10000| 1000|0.53807064| PASSED + rgb_permutations| 2| 100000| 100|0.72182964| PASSED + rgb_permutations| 3| 100000| 100|0.98132372| PASSED + rgb_permutations| 4| 100000| 100|0.32426721| PASSED + rgb_permutations| 5| 100000| 100|0.94033412| PASSED + rgb_lagged_sum| 0| 1000000| 100|0.78964883| PASSED + rgb_lagged_sum| 1| 1000000| 100|0.82881744| PASSED + rgb_lagged_sum| 2| 1000000| 100|0.90805811| PASSED + rgb_lagged_sum| 3| 1000000| 100|0.21428921| PASSED + rgb_lagged_sum| 4| 1000000| 100|0.67964064| PASSED + rgb_lagged_sum| 5| 1000000| 100|0.64463634| PASSED + rgb_lagged_sum| 6| 1000000| 100|0.94268050| PASSED + rgb_lagged_sum| 7| 1000000| 100|0.33288741| PASSED + rgb_lagged_sum| 8| 1000000| 100|0.79565468| PASSED + rgb_lagged_sum| 9| 1000000| 100|0.26291492| PASSED + rgb_lagged_sum| 10| 1000000| 100|0.23887951| PASSED + rgb_lagged_sum| 11| 1000000| 100|0.75511918| PASSED + rgb_lagged_sum| 12| 1000000| 100|0.44806331| PASSED + rgb_lagged_sum| 13| 1000000| 100|0.57950849| PASSED + rgb_lagged_sum| 14| 1000000| 100|0.37109436| PASSED + rgb_lagged_sum| 15| 1000000| 100|0.89758378| PASSED + rgb_lagged_sum| 16| 1000000| 100|0.06000037| PASSED + rgb_lagged_sum| 17| 1000000| 100|0.87051454| PASSED + rgb_lagged_sum| 18| 1000000| 100|0.47509380| PASSED + rgb_lagged_sum| 19| 1000000| 100|0.34523765| PASSED + rgb_lagged_sum| 20| 1000000| 100|0.98418490| PASSED + rgb_lagged_sum| 21| 1000000| 100|0.17652983| PASSED + rgb_lagged_sum| 22| 1000000| 100|0.17374595| PASSED + rgb_lagged_sum| 23| 1000000| 100|0.63170066| PASSED + rgb_lagged_sum| 24| 1000000| 100|0.28953394| PASSED + rgb_lagged_sum| 25| 1000000| 100|0.64849257| PASSED + rgb_lagged_sum| 26| 1000000| 100|0.78468683| PASSED + rgb_lagged_sum| 27| 1000000| 100|0.36484261| PASSED + rgb_lagged_sum| 28| 1000000| 100|0.66712700| PASSED + rgb_lagged_sum| 29| 1000000| 100|0.48604229| PASSED + rgb_lagged_sum| 30| 1000000| 100|0.17591412| PASSED + rgb_lagged_sum| 31| 1000000| 100|0.32433586| PASSED + rgb_lagged_sum| 32| 1000000| 100|0.12971163| PASSED + rgb_kstest_test| 0| 10000| 1000|0.47803450| PASSED + dab_bytedistrib| 0| 51200000| 1|0.29203091| PASSED + dab_dct| 256| 50000| 1|0.06993184| PASSED +Preparing to run test 207. ntuple = 0 + dab_filltree| 32| 15000000| 1|0.38387488| PASSED + dab_filltree| 32| 15000000| 1|0.55295440| PASSED +Preparing to run test 208. ntuple = 0 + dab_filltree2| 0| 5000000| 1|0.24922510| PASSED + dab_filltree2| 1| 5000000| 1|0.98227801| PASSED +Preparing to run test 209. ntuple = 0 + dab_monobit2| 12| 65000000| 1|0.71153312| PASSED +``` + +At 48 MHz and a core voltage of 0.95 V, there would be three or four `WARN`ings. + +### Throughput + +The TRNG is rather CPU-intensive (due to its IRQ-blocking busyloop) and not +very fast. The "cooked" (whitened) throughput is about 10 times higher than the +"raw" throughput. + +| System clock | Raw TRNG throughput | +|:------------ |:------------------- | +| 48 MHz | 10 kbit/s | +| 125 MHz | 40 kbit/s | +| 250 MHz | 50 kbit/s | + +## What is missing? + +* **Online health tests**: I don't know how to do this. I don't know what a + "chi-squared" is. Health tests are rather important for TRNGs (to make sure + nothing weird is happening), so this is not a small downside. +* **DMA+PIO-based implementation**: to relieve the CPU from some of its load. + Currently, it's 100% a spinloop on the CPU, with interrupts disabled. +* **Multicore mutex**: currently, both CPU cores could run this code, which + would then modify the ROSC `COUNT` register in parallell. This is bad and + will lead to bad results. Don't do this. (Or alternatively, add mutex guards, + or bug me to do so.) +* **More useful `rand()` functions**: currently, the code just gives you bits, + and turning these bits into a random integer in a range, or a random float, + ..., is left as an exercise for the reader. +* **Protections against physical attacks**: if an adversary is in the physical + proximity of the device, it's game over. They could attach an SWD debugger to + the RP2040 (it has no protection against this), use a power side channel or + EM emanations to look at the ROSC doing its oscillate-y thing, or tamper with + the environment (e.g. freeze spray or harmonic EM emissions) to slow down the + ROSC or make it lock onto a different frequency. I don't plan to add any + defenses against such attacks. (Please get a properly certified hardware + module if such attacks are part of your threat model, I'm begging you, don't + use this.) + +## Usage + +Here's a quick example: + +```c +#include +#include + +#include "rorand.h" /* raw rng */ +#include "rourand.h" /* 'cooked'/whitened rng */ + + +static void health_alarm(const char* msg) { + // TRNG health alarm raised! + iprintf("TRNG health alarm! %s\n", msg); +} + +int main() { + /** raw randomness **/ + enum rourand_error r = rorand_init(health_alarm); + if (r != rr_ok) { + panic("rorand failed to initialize!"); + } + + // get 128 bits of raw randomness + uint8_t data[16]; + rorand_get(data, sizeof(data)*CHAR_BIT); // specified in bits + + /** 'cooked'/whitened randomness **/ + int rate = 0; // rate at which the CSPRNG will reseed with true random data. 0 is default. + // you (the user) must call rorand_init first before calling rourand_init! + struct rourand_state* ur = rourand_init(rorand_get, rate); + if (!ur) { + panic("rourand failed to initialize!"); + } + + rourand_get(ur, data, sizeof(data)); // specified in bytes + rourand_free(ur); +} +``` + +## License + +License is TBD. + +The [Ascon reference implementation](https://github.com/ascon/ascon-c) is +licensed under CC0 1.0. + +The [Raspberry Pico C SDK](https://github.com/raspberrypi/pico-sdk) is licensed +under the BSD-3-Clause license. + diff --git a/img/jitter.png b/img/jitter.png new file mode 100644 index 0000000000000000000000000000000000000000..26aa2f2b7092dcf7b887839a5c5525b8d86c4ea8 GIT binary patch literal 9917 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 uf4^(`kNLXk|C#y!@I}`DpUYs{X=i_G(Q)