AMD's random number generator can't generate a 0?
Posted by BruceEel 14 hours ago
Comments
Comment by jstanley 12 hours ago
Do we now learn that they fixed "always generate all 1s" with "never generate all 0s"??
EDIT: I've been unable to reproduce the problem on my CPU, FWIW. It's a Ryzen 5 3600.
EDIT2: OK, update, I can reproduce it with rdrand16, rdrand32 is fine but rdrand16 can never generate all 0s. So my CPU does have this problem!
Comment by 0x000xca0xfe 11 hours ago
But it looks like the rdrand16 instruction can produce zeros just fine, it just sets CF=0 erroneously (indicating an error and that the user program should retry).
So keep that in mind when you try to reproduce it too and use some abstraction that could implement retries internally.
Comment by ComputerGuru 4 hours ago
Zen 5 rdrand16/32 return zero with CF=1 on entropy exhaustion and their recommended approach directly leads to the issue you observed: treat all-zero result of rdseed as if cf=0 (failure) and re-roll the dice, effectively recreating the zen 1/zen 2 issue all over again!
They say this might be addressed by a future microcode update… meaning there’s a chance they’ll just patch it to do just that in software. Maybe that’s how they got into this mess in the first place?
Also, am I a complete idiot or is asserting the relative distribution of a mere 64k possible results a rather easy black box validation test that I would’ve assumed they’d be doing? When I used to write cycle-accurate emulators in the past, that would have been an obvious test to include. This isn’t some arcane instruction no one uses or a really complicated case with deep dependency and/or timing issues; it’s like getting rdtsc wrong.
Comment by dooglius 11 hours ago
Comment by 0x000xca0xfe 9 hours ago
Here are some stats:
Rounds (N): 1000000000
Failed (F): 15312
Valid (V): 999984688
N/65536: 15258.789
V/65536: 15258.555
Failed, result was zero: 15312
Failed, result non-zero: 0
Bucket value for 0: 15312
Bucket value for 1: 15290
Bucket value for 65535: 15223
Min bucket value: 14670
Max bucket value: 15835
I used this C program to collect them: #include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
const size_t N = 1000000000; // 1e9
struct rdrand16_result {
uint16_t n;
bool ok;
};
static inline struct rdrand16_result rdrand16()
{
struct rdrand16_result result;
__asm__ __volatile__( "rdrand %0" : "=r" (result.n), "=@ccc" (result.ok) );
return result;
}
int main()
{
size_t buckets[0xFFFF + 1] = { 0 };
size_t notok = 0, notok_zero = 0, notok_nonz = 0;
for (size_t i = 0; i < N; ++i) {
struct rdrand16_result result = rdrand16();
++buckets[result.n];
if (! result.ok) {
++notok;
notok_zero += result.n == 0;
notok_nonz += result.n != 0;
}
}
size_t max = 0, min = N;
for (size_t i = 0; i <= 0xFFFF; ++i) {
size_t n = buckets[i];
min = n < min ? n : min;
max = n > max ? n : max;
}
printf("Rounds (N): %zu\n", N);
printf("Failed (F): %zu\n", notok);
printf("Valid (V): %zu\n", N - notok);
printf("N/65536: %.3f\n", (double)N / 65536);
printf("V/65536: %.3f\n", (double)(N - notok) / 65536);
printf("Failed, result was zero: %zu\n", notok_zero);
printf("Failed, result non-zero: %zu\n", notok_nonz);
printf("Bucket value for 0: %zu\n", buckets[0]);
printf("Bucket value for 1: %zu\n", buckets[1]);
printf("Bucket value for 65535: %zu\n", buckets[0xFFFF]);
printf("Min bucket value: %zu\n", min);
printf("Max bucket value: %zu\n", max);
return 0;
}Comment by eigenform 1 hour ago
Confusingly, the AMD programming manual (Rev. 3.38 - July 2026) only explicitly states this ("that the result is always zero when CF=0") in the description of RDSEED, but the Intel SDM mentions this in the description of both instructions.
Comment by yk 12 hours ago
return 4 # Determined by fair dice roll.Comment by rbanffy 11 hours ago
Comment by Gander5739 12 hours ago
Comment by lathiat 12 hours ago
Most of the console hacking talks are great, both informative and entertaining.
Comment by einsteinx2 11 hours ago
That presentation is awesome though, worth a watch either way!
Comment by adastra22 7 hours ago
Comment by Liquid_Fire 6 hours ago
The comic was published on 9 February 2007 [0].
The PS3 was first released in November 2006. I haven't watched the video yet, but its description says "2010 saw the first hacks for the Playstation 3".
Comment by matja 11 hours ago
Comment by peri-cl 11 hours ago
$ ./a.out | rg '\b\-?\d\b' | sort -n | uniq -c
15281 -2
15192 -1
15273 0
15243 1
15269 2
I used the GCC intrinsic ( _rdrand16_step ), #include <immintrin.h>
short rdrand16() { // gcc -mrdrnd
short ret;
while (1 != _rdrand16_step(&ret)) { }
return ret;
}Comment by jamesponddotco 9 hours ago
I.e., we had `random.trust_cpu=off nordrand` in `GRUB_CMDLINE_LINUX`.
Comment by knorker 9 hours ago
I thought the kernel would not replace anything just because it adds a potentially bad source.
E.g. if you have rand source A, and xor it with rand source B, then you get, at worst, the best of A and B,
Comment by edelbitter 6 hours ago
a) whether you use the maybe-entropy provided by the CPU (and/or the bootloader)
b) whether you credit that maybe-entropy towards your tracking of whether the pool should be considered sufficiently seeded
random.trust_cpu/random.trust_bootloader configures b).
nordrand has been removed from the kernel as it had become overloaded by meaning both a) and b)
Under most circumstances, a) is harmless. You mostly want that off when the CPU exhibits some performance hiccups when asked.
Under some circumstances, b) is outright dangerous. Some applications can work without seeded pool at some slightly reduced performance, but could be made to fail miserably if they had been made to believe that the pool was seeded yet it was not. This happens with hash tables when you skip some of the accounting because it seems no longer relevant. It really would not be relevant, once even a determined attacker should be unable to reliably trigger the worst-case-performance.
Comment by leni536 3 hours ago
With the assumption that sources A and B are independent from each other.
Comment by knorker 3 hours ago
In the context of this topic, it's a bit pedantic.
Comment by jamesponddotco 9 hours ago
Comment by wahern 6 hours ago
What it is is unreliable. And that's fine so long as you have other entropy sources. OpenBSD is really good about this. Quite a few drivers for various chipsets and cards exist just to read their RNGs, not actually use them for their primary function (which can be a bummer if you want to use the the device, get your hopes up when you see the driver exists in the tree, then discover the only capability it supports is reading the RNG). If you have a CPU with a known bad rdrand, odds are OpenBSD is still sourcing strong randomness from some other chip in your system (PSP, NIC, etc). And because feeding bad (as opposed to malicious[1]) entropy is harmless[2], they don't have to maintain a pile of conditions. Nobody is worse off, and overall everybody is better off, including having stronger getrandom/getentropy output, by not trying to be clever.
[1] https://blog.cr.yp.to/20140205-entropy.html
[2] Presuming nothing is relying on an entropy estimator. I can't remember if Linux finally moved past the entropy estimator nonsense. IIRC they did add a software jitter RNG that runs early to try to set a minimum entropy floor, regardless of hardware sources.
Comment by knorker 3 hours ago
Well, if you literally have nothing else, then you don't have an option anyway, so the whole question is moot.
Except yeah if literally the only way to collect entropy in your system is the platform's opaque RNG, then sure this means your risk assessment should list that as a SPOF. But by definition these cases only have that option, so you can't do anything else.
In reality, you can probably do something else in all but the most extreme embedded environments.
Comment by adastra22 7 hours ago
Comment by knorker 3 hours ago
Yes and no. Mostly no.
In a simplified model, it's only useless if it adds zero bits of entropy. But if a source that's supposed to add 128 bits of entropy only adds 16, well, it's still 16.
I would never trust RDRAND on its own. If nothing else because it's always subject to a microcode backdoor. But if I already have something I'm happy with the entropy of, sure, I'd XOR it with RDRAND output. It cannot make it worse.
Comment by teravor 7 hours ago
Comment by adastra22 7 hours ago
Comment by teravor 7 hours ago
if your algorithm controls a source of entropy and can inspect the other sources, it can craft its source to bias the result. a fanciful attack but it means you should at least discriminate what you put into the pool.
Comment by tptacek 6 hours ago
Comment by teravor 5 hours ago
> it becomes dangerous if they can preview the results or inspect the other sources
because the malicious source can just precompute the hash for the bias it wants.Comment by adastra22 4 hours ago
Comment by teravor 3 hours ago
while you cannot take control over the hash output you can bias it because you have multiple tries. that's how bitcoin mining works too...
for cryptographic applications any bias can be engineered to be fatal in one way or another.
Comment by knorker 3 hours ago
Yes. I don't find this a particularly interesting scenario, though. Sure, we can come up with stuxnet-like airgap attacks where we on-device, but not remotely, can read entropy sources. AND we can modify the output of RDRAND. And there keys have been generated for data we can later intercept. But despite that control (potentially on a CPU microcode level) we are unable to stegonographically leak it?
Sure. Possible. Has it ever happened?
Comment by RandomOnyx 12 hours ago
Basically I'm wondering if it's a bug in the version of the instruction that writes to a 16-bit reg, or a bug in the underlying RNG
Comment by jstanley 12 hours ago
Comment by goalieca 12 hours ago
Comment by jstanley 11 hours ago
Comment by RandomOnyx 12 hours ago
*: missed a word the first time around
Comment by JdeBP 12 hours ago
Comment by rbanffy 11 hours ago
To prove it, we'd need to examine the chip and its microcode.
Comment by zir_blazer 2 hours ago
https://www.phoronix.com/news/AMD-Releases-Linux-Zen2-Fix
https://arstechnica.com/gadgets/2019/10/how-a-months-old-amd...
No idea what happened after. And that also means that you suddently need information about user systems BIOS/Microcode.
Comment by strenholme 11 hours ago
It takes entropy from multiple different sources, makes it all input to the XOF, then the XOF uses cryptography to output a stream that has as much entropy as the combined entropy of all of its sources of randomness. So if an XOF, for example, takes 100 runs of rdrand16, along with the system time in microseconds and the number of milliseconds between receiving 100 packets over the network, the XOF will output a completely random stream without artifacts like never returning 0x0000, even if rdrand16 never outputs 0x0000.
Comment by stingraycharles 11 hours ago
I fail to see why one should either rely on a single random source nor roll their own.
Comment by strenholme 11 hours ago
getrandom() is often times suggested, but alas isn’t a standardized function, i.e. it’s not part of the POSIX specification. Considering how the C23 changes to the C specification caused a lot of perfectly good C code to no longer compile, I’m very anal about sticking to specs; I use '-std=C99' for my code these days (even though it can compile as C23 code) and stick to POSIX functions (except chroot() and setgroups(), but both of those predate POSIX, and even here I have a compile-time option to compile my code without those non-POSIX syscalls).
The code using a secure XOF (the algorithm was developed by the same team which later on made SHA-3, and includes people who helped make AES) has been around for nearly two decades (the code where I roll my own RNG to make secure random numbers has been around for over 25 years, but used AES before XOFs existed) and not one security problem has found with the RNG code has ever been found. [1] “Don’t roll your own RNG” is a suggestion, but it is possible to do so securely if one knows what they are doing (i.e. they have read Applied Cryptography and keep current with cryptographic developments).
For anything vibe coded (my code is 100% human written, for the record), rolling one’s own RNG is a really bad idea.
[1] There was a theoretical issue with cache timing attacks over two decades ago, so I put mitigations in place, and then chose to use an XOF for newer code.
[2] There was an issue where a separate implementation I made of this XOF would generate incorrect test vectors in clang, but only at some optimization levels. I now test the XOF in both GCC and clang at multiple optimization levels to make sure it acts correctly.
Comment by cesarb 7 hours ago
Is /dev/random or /dev/urandom part of the POSIX specification?
Comment by strenholme 4 hours ago
I actually at one time had a Windows binary which would use Windows proprietary calls to make a “urandom” file (secret.txt was its name) so people could have good entropy on systems using the exact same interface as fopen("/dev/urandom","rb") (i.e fopen("secret.txt","rb")) without needing an actual /dev/urandom.
Comment by NooneAtAll3 10 hours ago
so instead you suggest trusting your own untested unlooked at implementation more?
Comment by strenholme 10 hours ago
>untested
The automated tests includes tests that make sure the XOF is correctly implemented. [1]
>unlooked at
People have been looking at my code for security holes for well over 20 years, and I have been getting multiple AI assisted security reports over the last year, things like “there’s a buffer overflow in this code which is nay to impossible to exploit, using code which hasn’t even been able to compile since 2022”.
[1] https://github.com/samboy/MaraDNS/tree/master/deadwood-githu... and https://github.com/samboy/MaraDNS/tree/master/deadwood-githu...
Comment by SideQuark 10 hours ago
You’re correct about black and white thinking. Then you invoke multiple straw men in this thread to defend that you’ll roll your own.
Disclaimer: I’ve been hired for multiple DoD projects to break hardware and software security systems, and I nearly always succeed, because so many people (and companies) roll their own.
Comment by strenholme 9 hours ago
One reason why I don’t change the RNGs used in my code is because I know how dangerous playing with RNG code is. For example, one implemention I wrote of the XOF—not one I used in production code, mind you—generated incorrect vectors, but only in clang and only at some levels of optimization. Needless to say, I now have a test to make sure my XOF code generates correct vectors with both GCC and clang at multiple different optimization levels.
People have brought up CVE-2008-0166 in this thread, but the Coldcard incident from this year (where people literally lost millions of dollars) also comes to mind, so I’m aware how dangerous playing with RNG code is.
That’s why the code is basically the same code I had 18 years ago, and why I (as well as multiple people running AI-assisted security audits) have extensively tested that code.
The proof is in the pudding: No security issues have ever been found with the XOF PRNG, and it’s been nearly two decades.
(I also think “straw men” is being used incorrectly here; most likely the parent poster thinks I was implying that Linux’s /dev/urandom is insecure but the actual argument is that my code runs on a lot more than just Linux, and some of those systems could have an insecure /dev/urandom)
[1] As per https://blog.cr.yp.to/20140205-entropy.html as long as we’re not using a malicious source of entropy, but said malicious source will need to perform 2^n operations of the XOF to generate n bits of controlled output, and only in the case if said malicious entropy source can somehow know the output of the other entropy sources, especially since the XOF is seeded once then run indefinitely in my code.
Comment by seanhunter 4 hours ago
Isn’t that also true of the cryptographic sponge function that is used to implement /dev/{,u}random?
Comment by tourist2d 5 hours ago
Comment by UnlockedSecrets 10 hours ago
Comment by strenholme 9 hours ago
https://maradns.blogspot.com/2010/07/radiogatun32-passes-all...
Comment by creatonez 4 hours ago
Comment by sltkr 10 hours ago
The POSIX standard function is getentropy(), which internally calls getrandom() on Linux.
> what if there’s a bug in the kernel which causes /dev/(u)ramdom to be less than secure?
It's often the other way around: the Linux kernel contains thousands of workarounds for buggy hardware, while the buggy hardware itself doesn't always get patched. Linux developers take this stuff very seriously. As a result it's often safer to rely on kernel APIs than to access the hardware directly.
The kernel code involving random number generation receives an exceptionally high amount of scrutiny because of its security implications, so I'd trust it to do the right thing over a naked call to RDRAND which nobody knows how exactly it's implemented in proprietary hardware or a handrolled solution to mix the RDRAND output with other entropy sources.
Remember the Debian openssl disaster from 2008? That happened exactly because someone had handrolled their entropy mixing solution, then someone else broke it.
Comment by strenholme 10 hours ago
“The intended use of this function is to create a seed for other pseudo-random number generators”
So, if I were to use genentropy() in a POSIX-compliant way, I would need to do what I already do: Use my own pseudo-random number generator.
The Debian openssl disaster (CVE 2008-0166, I remember it well) was caused because someone incorrectly patched secure code: Since the code used uninitialized memory as one of many entropy sources, which causes Valgrind to complain, they patched the code to not use uninitialized memory for entropy, but then accidentally disabled all other sources of entropy (except the 16-bit PID). It was caused because the person making the patch didn’t fully understand why it was a good idea to, in that context, use code which Valgrind complained about. [1]
As an aside, here’s how I deal with those Valgrind errors:
#ifdef VALGRIND_NOERRORS
/* Valgrind reports our intentional use of values of uncleared
* allocated memory as one source of entropy as an error, so we
* allow it to be disabled for Valgrind testing */
memset(noise,0,512);
#endif /* VALGRIND_NOERRORS */
I do believe the Linux Kernel does have secure RNG code, but I also write code which has run on a lot of different systems and environments, including embedded ones, and some of them might not have a secure /dev/urandom.[1] Debian has a lot of inflexible policies like this which can cause problems. Another issue Debian has is they have a policy a given piece of code must always compile to the same binary on a given architecture. That isn’t true with the unpatched version of my code, because the hash compression routine uses a 32-bit random number generated at compile time to avoid hash collision attacks (it also uses another 32-bit random number at runtime, and I make sure the hash compression values are never visible). So the Debian version of my code was forced to be patched to be less secure.
Comment by wahern 12 minutes ago
Comment by jcranmer 8 hours ago
But it gets worse. If the optimizer sees that you're loading uninitialized memory, it can reason that since the result of uninitialized memory is garbage, doing any computation on that result is also garbage, and happily delete said computation as a result. The cascading effect of this is to delete all of the entropy-mixing code, leaving your entropy pool with only the very low entropy source--giving uninitialized memory effectively negative entropy.
The net effect is that, at least for me, seeing someone trying to seed an entropy pool with uninitialized memory is a giant neon flashing sign saying "do not trust this code." It provides at best very little entropy and at worst actively destroys entropy and has other calamitous effects like valgrind or sanitizer errors, so you need to have other entropy sources anyways, so why bother?
Comment by strenholme 6 hours ago
This is an interesting assertion, and one that is easy enough to prove true.
Let’s take the following C code, which uses the same XOF algorithm (but not implementation) as my application (Deadwood):
#include<stdio.h>
#include<stdint.h>
#include<stdlib.h>
#define b(z) for(c=0;c<z;c++)
uint32_t c,e[42],f[42],g=19,h
=13,n[45],i,j,k;void m(){j=0;
b(12)f[c+c%3*h]^=e[c+1];b(g){
i=c*7%g;k=e[i++];k^=e[i%g]|~e
[(i+1)%g];j=j+c;n[c]=n[c+g]=k
>>j%32|k<<-j%32;}for(i=39;i--
;f[i+1]=f[i])e[i]=n[i]^n[i+1]
^n[i+4];b(3)e[c+h]^=f[c*h]=f[
c*h+h];*e^=1;}int main(int c,
char**v){char*q=malloc(2);if(
q==0)return 0;q[0]&=31;q[0]|=
1;q[1]=0;for(;;m()){b(3){for(
j=0;j<4;){f[c*h]^=k=(*q?255&
*q:1)<<8*j++;e[c+16]^=k;if(!
*q++){b(18)m();b(8){j=c;b(1)
printf("%02x",(e[1+j%2]>>8*c)
&255);c=j;if(c%2)m();}puts(
"");return 0;}}}}}
This code, as I’m sure the parent poster can clearly see, uses four bits of uninitialized allocated memory as its source of entropy. As per the parent’s assertion, there should therefore exist a compiler whose optimizer will cause this XOF to not correctly run.The above code can have one of the following possible 16 outputs:
0a5d51f3745c7266
f84b051f67115f1a
f87105c4ecfefe67
92074ac8e1e7a42e
1441ac245f288e18
87023372e57ae001
047a3ddd14209546
340b2ff47c61172e
bfb9289ed096f977
dfd56a7a8d7d723e
2151460954a80242
6822335c6e0160dc
3783ce3cae3d0774
4e0156df46c00bac
69795d939d211e7a
If the above code has any but one of the above 16 outputs, this is a real world case where a C compiler, seeing uninitialized memory being used, optimizes out the code which uses said uninitialized memory as an input, and therefore will not output one of the above 16 possible words.I’ve tested the above code in GCC -O3 and clang -O3; both generate one of the above 16 possible outputs (each one generating a different output).
If there really is a compiler out there which does “happily delete said computation”, which would give a different output than one of the 16 outputs above, please name that compiler, the version of said compiler used, and all compile-time flags used with said compiler.
While I’ve never heard of a real world case where a compiler would refuse to run code using uninitialized memory as yet another source of entropy for a secure PRNG, I do know of a real world case where a very nasty security hole was caused because someone incorrectly removed code using uninitialized memory as part of an entropy pool: CVE-2008-0166
Comment by titzer 4 hours ago
This entire thread has a lot of "no security issues have ever been found in my code, and I test a lot. Therefore no bugs will ever exist in my code and we're all safe." To see you doing this in an explicitly security-conscious setting is distressing.
If anything, I see assertions like this and juxtaposed with blatant, willful misunderstanding of how C and C compilers work and it does the opposite of inspiring confidence.
Look at CVE-2009-1897; this is the classic example of how C compilers are happy to try to optimize code in the face of UB and lead to worse problems.
> If the above code has any but one of the above 16 outputs
I don't think you understand how insane optimizations in the face of UB can be. Just go look at this issue:
https://github.com/llvm/llvm-project/issues/174844?utm_sourc...
Comment by wat10000 2 hours ago
Here's a little example of code disappearing due to a read of uninitialized memory:
void test(int x) {
int uninit;
puts("hello");
if (uninit)
puts("non-zero");
else
puts("zero");
}
clang 23.1.0 -O3 targeting ARMv8 deletes both branches of the if. Not only that, it deletes the code to return from the function. The very last instruction of the function is `bl puts`, meaning that after puts returns, it will start executing whatever function happened to come after this one in memory. That's probably a good thing in context, because that's likely to crash or infinite loop and make it clear that something went badly wrong, but the failure could easily be something more subtle that just disables some random seeding while otherwise executing normally.Comment by strenholme 46 minutes ago
It’s not clear whether that is the memory location malloc() returns or the memory pointed to by malloc(), but based on the next item in the list of cases where behavior is undefined, we have “The value of any bytes in a new object allocated by the realloc function beyond the size of the old object are used [results in undefined behavior]”.
The good news is that, as Taek and sltkr have pointed out elsewhere in the thread, clock_gettime() gets us a tiny bit of entropy, not perfect, but better than nothing. clock_gettime() is also POSIX compliant, although I remember about 15 years ago macOS didn’t support clock_gettime() (I checked, and it does these days).
getentropy() will become better than /dev/urandom for kernel level random numbers, but the problem is that getentropy() was only standardized and added to POSIX in 2024—too recent for me to feel 100% sure it’s widely implemented. And, yes, /dev/urandom (like chroot(), like sergroups()) isn’t defined in POSIX but it’s widely used.
Comment by boltzmann64 10 hours ago
Comment by akerl_ 10 hours ago
Comment by Vvector 9 hours ago
Matt Mackall: "It's worth noting that the maintainer of record (me) for the Linux RNG quit the project about two years ago precisely because Linus decided to include a patch from Intel to allow their unauditable RdRand to bypass the entropy pool over my strenuous objections. "
https://cryptome.wikileaks.org/2013/07/intel-bed-nsa.htm?utm...
Comment by matja 6 hours ago
Comment by tptacek 6 hours ago
Comment by Joel_Mckay 4 hours ago
Instead, people used haveged to workaround the issue. Not a conspiracy by some dude, but rather just more budget hardware limitations.
Don't worry about it, there are lots of real dubious things people do already. =3
Comment by akerl_ 3 hours ago
Comment by Joel_Mckay 3 hours ago
I don't like piling on people that engage in good faith. Best regards =3
Comment by akerl_ 3 hours ago
Comment by Joel_Mckay 2 hours ago
Perhaps it is time to get outside for a walk to lower stress levels. =3
Comment by akerl_ 2 hours ago
Comment by akerl_ 8 hours ago
Comment by sltkr 10 hours ago
The only legitimate reason to roll your own is when you're developing for an embedded system or a bootloader or something like that where there is no kernel API available.
Comment by strenholme 10 hours ago
Comment by ironhaven 7 hours ago
If anyone is interested in this topic please just read the code[0]. It has a lot of interesting tricks that you would not have just rolling your own.
[0]https://github.com/torvalds/linux/blob/master/drivers/char/r...
Comment by tptacek 6 hours ago
But if you're using a custom kernel that has a custom KRNG based on an XOF, sure, whatever, I guess.
Comment by iainmerrick 9 hours ago
Oh, I guess you have to ensure the inputs aren’t correlated, or they’ll cancel out?
Comment by strenholme 8 hours ago
The sources of entropy can be correlated and won’t cancel out with a well designed secure XOF. SHAKE-256 is an example of a secure XOF.
Comment by Taek 10 hours ago
hash = sha256(current_time());
for i := 0; i < n; i++ {
hash = sha256(hash.append(current_time()))
}
This is because the number of nanoseconds between hashes is actually itself variable, and this is true for physics reasons that are basically beyond the control of any attacker trying to manipulate your entropy. If your time() function has a resolution of nanoseconds, you only need your loop to iterate about 50 times to get a cryptographically secure amount of entropy. If your time() function has a resolution of milliseconds, you need to let this run for more like 20 milliseconds, and if your time() function has a resolution of seconds you need to let it run for more like 5 seconds.The reason I like doing it this way is that it happens entirely in userspace, it's genuinely a secure method of generating entropy, and it has no dependencies on potentially buggy firmware or microcode outside of the time() call, which is both fairly narrow, fairly heavily used (meaning a bug is likely to be discovered during testing, as the implementation is likely heavily scrutinized), and also fairly easy to test independently - just look at the number of nanoseconds that elapse at each consecutive call to sha256(current_time()) and verify that there's some statistical variance. The above suggestions are assuming about 2.5 bits of variance between calls, meaning there should be a range of at least 20 nanoseconds between your slowest and fastest hash call. This has been true on every CPU I've ever measured, including microcontrollers.
Comment by sltkr 10 hours ago
The security of your system depends on time() providing enough entropy, even though that's not what it's designed to do. It's built on top of the wrong primitive from the start.
> The reason I like doing it this way is that it happens entirely in userspace
On Linux this is often true, but there is no portable way to get the current time that is _guaranteed_ not to do any system calls.
> If your time() function has a resolution of nanoseconds, you only need your loop to iterate about 50 times to get a cryptographically secure amount of entropy.
You haven't proven that at all. It's easy to imagine that on a CPU running at a fixed frequency the interval between reads is constant, so if anyone knows (or can guess) the start time the resulting seed is entirely predictable.
This is completely independent of timer resolution. You seem to realize that as you were writing that:
> just look at the number of nanoseconds that elapse at each consecutive call to sha256(current_time()) and verify that there's some statistical variance
Oh yes, because evaluating the quality of a random number generator is such a trivial thing to do, it's not like there is decades of research behind it or anything.
And assuming you are able to verify the statistical variance: are you going to put that logic in the loop, making it significantly more complex?
Or are you going to do this test on your machine and then ship your code on the assumption that if it works on your machine, it will work everywhere else, too?
> if your time() function has a resolution of seconds you need to let it run for more like 5 seconds.
So not only is it insecure, it's agonizingly slow by design. Why do a system call that takes milliseconds at best, when we can run a loop in userspace for 5 seconds?
All this just so you can avoid writing the obviously correct oneliner:
if (getentropy(&seed, sizeof(seed)) != 0) abort();Comment by alerighi 9 hours ago
Sure an infected system may as well fake time values, but that is much more difficult and it's possible to detect from a userspace program. For example you mention to use getentroy, but on a compromised system you know how easy it is to change something that is implemented in a system library (e.g. libc) or even if you read /dev/random directly without passing from the libc how easy it's to make it read whatever you want?
To me that is not that bad implementation, in fact it's an implementation that is used in a lot of security software (including GPG, not as the sole source of course but as one of many).
Comment by sltkr 9 hours ago
A compromised kernel doesn't even have to fake any data. It can just read the generated seed directly from user space without the program ever knowing about it.
> Sure an infected system may as well fake time values, but that is much more difficult
clock_gettime() just reads a value that the kernel has set, so that's not particularly difficult to fake.
If you're thinking of using RDTSC instructions directly, that's of course not portable, and at that point you might as well call RDRAND directly, which is at least designed to provide random data.
> it's possible to detect from a userspace program.
There is no detection that is guaranteed to work on a compromised system.
And whatever detection you have in mind to make the algorithm resistant to tampering was _not_ part of the original for-loop. You cannot claim the for-loop is superior to just calling getentropy() because it "can detect" clock tampering, while handwaving away the actual code to detect this clock tampering.
> it's an implementation that is used in a lot of security software (including GPG, not as the sole source of course but as one of many).
It's fine if you use it as a strictly additional source of entropy, but then the whole argument that it is superior because it avoids syscalls goes out of the window, because you're doing strictly _more_ work.
Comment by Taek 9 hours ago
And, I agree that if the system is compromised to the level that the attacker can control the output of the timer, it's probably compromised to the level that the attacker can just read your generated entropy straight from memory.
The point here is not to be fast, it's to be protected against implementation bugs on systems that weren't designed by security professionals.
Comment by creatonez 3 hours ago
You mean javascript libraries that do a bit of Math.random() and a miniscule amount of mixing, that had been widely considered poor practice for years while old bitcoin wallet generator websites were burning users with it?
Has any actual serious CSPRNG exposed bitcoin wallets?
Comment by Taek 1 hour ago
Android SecureRandom (2013)
https://android-developers.googleblog.com/2013/08/some-secur... CryptoJS / Ill Bloom (2026)
https://illbloom.org/articles/cryptojs-vulnerability/ Trust Wallet Browser Extension (2023)
https://www.ledger.com/blog/funds-of-every-wallet-created-wi... Libbitcoin / Milk Sad (2023)
https://milksad.info/disclosure.html Trust Wallet iOS / Trezor Library
https://secbit.io/blog/en/2024/01/19/trust-wallets-fomo3d-su...Comment by alerighi 8 hours ago
If you trust TPM not to be backdoored... come on, you don't think the NSA or who else has put effort in getting a backdoor inside? They even tried to put one in Linux and it's documented, never the less in anything proprietary...
> It can just read the generated seed directly from user space without the program ever knowing about it.
Not that simple: it has to know exactly where in memory it's stored, and that requires understanding of the source code of the program that is encrypting data. That is not of course a simple task if someone wants to write a malware that just "steals" encrypted data from any software just by looking at the network traffic, like you would do if you compromise the RNG of the OS.
> clock_gettime() just reads a value that the kernel has set, so that's not particularly difficult to fake.
You can sample the call millions of time and understand if the value is truly random or there is a pattern. It's something detectable. Software like GPG that doesn't trust what the OS gives you already do that (as well as combining multiple entropy sources).
> It's fine if you use it as a strictly additional source of entropy, but then the whole argument that it is superior because it avoids syscalls goes out of the window, because you're doing strictly _more_ work.
Avoiding the syscall could have other benefits, not only performance. For example: a program making that syscall may be flagged by a possible backdoor as a process with something interesting in it, and thus a potential spyware may be interested in take, for example, the memory image of that program and send it to a remote system for it to be analyzed. The fact that the reading of the current time doesn't pass from a system calls means that it's not possible to identify that process as "some process that uses cryptography and thus has something interesting in it to hide".
Comment by strenholme 4 hours ago
Exactly. The people who are so adamant that one shouldn’t roll their own crypto are people who think we should just blindly trust the kernel to always return secure random numbers which haven’t been backdoored.
Now, in the real world, if they control the kernel’s RNG, they control a lot more than the RNG so any protection is an illusion. But blindly trusting a kernel’s RNG is something that makes some people understandably uncomfortable.
The decision I made to include a secure random number generator as part of my code in 2007 was the exact same decision DJB made to include a secure random number generator with his code in 1999, and it’s a decision I stand by: It never has had a known security problem, the FUD claiming otherwise isn’t backed up by evidence, and it makes a lot of sense in cross-platform code which targets embedded systems.
Comment by Taek 9 hours ago
Pretty much the only thing you can control when shipping software to many devices is that it runs on a physical CPU and has a timer. Every other RNG assumption over the decades has shown that sometimes someone upstream gets something catastrophically incorrect.
Comment by api 9 hours ago
Hardware RNGs can be one source, but no single source is trusted, and they're all combined in a way where even an intentionally malicious source is lost in noise and cannot actually determine output.
Comment by strenholme 8 hours ago
https://blog.cr.yp.to/20140205-entropy.html
Intel could much more easily compromise and attack systems than make an implementation of RdRand which is malicious in this manner.
Comment by tptacek 5 hours ago
Comment by api 8 hours ago
Comment by strenholme 8 hours ago
It’s like the attacks I occasionally see which are like “once we have administrator, we can attack the process because of this insecurity”. Well, yeah, but once we have administrator, we can read the entire memory of the “vulnerable” process and completely control its output too.
I’ve seen in the real world attacks where things were insecure because the PRNG wasn’t given enough entropy (CVE 2008-0166, Coldcard, etc.). I’ve never seen real world attacks where a PRNG was insecure from getting too much entropy.
Comment by Taek 8 hours ago
The value of the iterated hashing method is that it is dead simple and has little dependency on potentially buggy upstream code; it works even in very lightweight environments designed by engineers with no experience in security.
Comment by sltkr 9 hours ago
#include <time.h>
#include <stdio.h>
static int estimate_entropy(long l) {
int bits = 1; /* for the sign bit */
if (l < 0) l = -l;
while (l > 0) {
++bits;
l >>= 1;
}
return bits;
}
int main() {
struct timespec ts;
if (clock_getres(CLOCK_REALTIME, &ts) != 0) {
perror("clock_getres");
return 1;
}
printf("Clock resolution: %ld.%09ld\n", (long) ts.tv_sec, (long) ts.tv_nsec);
#define N 50 /* number of samples */
struct timespec samples[N];
for (int i = 0; i < N; ++i) {
clock_gettime(CLOCK_REALTIME, &samples[i]);
}
printf("Deltas (ns):");
long deltas[N - 1];
for (int i = 0; i < N - 1; ++i) {
deltas[i] =
(samples[i + 1].tv_sec - samples[i].tv_sec)*1000000000L
+ (samples[i + 1].tv_nsec - samples[i].tv_nsec);
printf(" %4ld", deltas[i]);
}
printf("\n");
long entropy = 0;
printf("Deltas of deltas: ");
for (int i = 0; i < N - 2; ++i) {
long dd = deltas[i + 1] - deltas[i];
printf(" %4ld", dd);
entropy += estimate_entropy(dd);
}
printf("\n");
printf("Maximum entropy: %lld\n", entropy);
}
On my system this prints: Clock resolution: 0.000000001
Deltas (ns): 55 51 23 23 25 24 24 24 24 24 25 25 24 24 24 24 24 25 24 24 24 25 25 24 24 23 25 24 24 25 24 23 25 25 26 23 25 24 24 25 26 24 23 25 25 26 24 25 24
Deltas of deltas: -4 -28 0 2 -1 0 0 0 0 1 0 -1 0 0 0 0 1 -1 0 0 1 0 -1 0 -1 2 -1 0 1 -1 -1 2 0 1 -3 2 -1 0 1 1 -2 -1 2 0 1 -2 1 -1
Maximum entropy: 92
So no, 50 iterations of that loop does not provide 256 bits of entropy due to random fluctuations in nanontime between calls.Comment by Taek 8 hours ago
You are not hashing between calls to the timer. The sha256 hash itself is responsible for doing physical things to the chip (heating up some parts unevenly during the hashing computation) which introduces meaningful entropy between calls to the current time.
You can't just do calls to clock_gettime(), you have do an actual sequential sha256() call between them. Please run this code again and tell me what results you get.
Comment by sltkr 7 hours ago
Case in point:
> The sha256 hash itself is responsible for doing physical things to the chip (heating up some parts unevenly during the hashing computation)
Some CPUs do thermal throttling, others run at a fixed frequency or are so underclocked that thermal throttling doesn't kick in during your 50 iterations. This is exactly the source of randomness that is just not guaranteed to exist across systems.
-----
> You can't just do calls to clock_gettime(), you have do an actual sequential sha256() call between them. Please run this code again and tell me what results you get.
OK, I'll humor you, but to reiterate: it isn't really my point.
After adding hashing in the loop:
Clock resolution: 0.000000001
Hash: a8531a79fc350a3b35b3e82e33b759f6caa97a12efd16a715acb99065b6f3e89
Deltas (ns): 21662 452 335 297 290 288 288 291 289 293 290 289 290 284 287 297 289 289 295 288 287 286 292 291 287 287 301 289 299 290 292 288 291 292 296 294 295 293 290 287 297 292 292 292 288 295 291 289 296
Deltas of deltas: -21210 -117 -38 -7 -2 0 3 -2 4 -3 -1 1 -6 3 10 -8 0 6 -7 -1 -1 6 -1 -4 0 14 -12 10 -9 2 -4 3 1 4 -2 1 -2 -3 -3 10 -5 0 0 -4 7 -4 -2 7
Maximum entropy: 177
Here it's mostly the first few iterations that are slow, the remaining ones are both fast and surprisingly consistent (the value 289 appears six times for example).It's more obvious if you run it a few times in a row:
Deltas (ns): 21662 452 335 297 290 288 288 291 289 293 290 289 290 284 287 297 289 289 295 288 287 286 292 291 287 287 301 289 299 290 292 288 291 292 296 294 295 293 290 287 297 292 292 292 288 295 291 289 296
Deltas (ns): 22213 486 361 318 290 290 290 289 289 291 289 291 287 289 285 289 294 289 289 287 294 292 293 292 295 295 286 298 288 291 292 295 291 292 291 292 297 294 293 297 289 288 299 288 299 295 292 291 293
Deltas (ns): 23042 475 312 309 290 292 294 291 291 289 290 293 287 291 290 297 299 288 289 294 289 289 297 294 295 295 288 295 291 287 290 287 300 293 289 290 292 287 293 295 292 291 289 292 288 294 290 287 290
Deltas (ns): 22209 478 360 301 295 293 290 291 290 290 293 284 291 290 289 290 294 289 294 293 290 301 288 298 287 295 300 295 292 300 293 296 295 294 294 293 291 289 295 293 291 299 292 299 292 291 295 298 292
The loop timings are quite consistent at least on a single system. That's a problem if an attacker is able to run the same program on the same system to establish baseline timings.If I estimate the entropy as the logarithm of the difference between maximum and minimum I get only 146 bits of entropy in this case. Technically above your standard of 128 bit, but my point was: nothing guarantees you get even this much entropy on a less noisy system.
This also shows the problem with your "just run more iterations" advice: in the above sample, the first five columns provide 24 bit of entropy per column, and the remaing 45 columns only 2.6 bits. So adding more iterations at the tail end wouldn't double the entropy obtained.
The code I used is here: https://pastebin.com/ZrL1UDEg
Comment by Taek 2 hours ago
Hashing is particularly chaotic because it lights up a different set of transistors on each clock cycle, which means the hotspots on the chip are being jerked around. Some transistors are going to light up 5-10 times in a row, and others are going to be idle 5-10 times in a row, and then randomly that changes. And all of this changes the number of picoseconds that it takes for a clock cycle to complete, which means that each clock cycle is genuinely going to take a different amount of time to complete, and stuff like temperature throttling is completely not at play whatsoever, because we're not talking about chip-wide temperatures, we're literally talking about temperature deltas between transistor a and transistor b.
That makes it a really wonderful source of entropy for cryptographic applications, because the CPU clock is so critical that it's almost never buggy (especially relative to other components that provide entropy), it's also almost impossible to manipulate reliably by an attacker (unless the attacker has an exploit that allows them to set the value of the clock directly - which is possible, but it's a very narrow surface area relative to other entropy sources), and you can completely take advantage of this entropy entirely in userspace, which once again heavily minimizes attack surface area and exposure to bugs.
I have searched far and wide for a CPU that does not reliably generate entropy using the iterated-hashing-against-the-clock method, and I have not found a single example of a CPU that consistently takes the same amount of time to complete a hash. And the reason isn't implementation, the physics of CPUs simply insist on introducing entropy when trying to repeatedly hash something quickly.
Comment by Taek 9 hours ago
I have tested this method on over 100 different CPUs and I have never seen such consistent output. I'm genuinely surprised to see that you only hit 92 bits of entropy, but that can trivially be fixed by doing 10x the iterations. 500 iterations is still going to put you under a millisecond of cost.
And, for what it's worth, code I've actually shipped has combined the above technique with Fortuna, and has typically targeted 2000 bits of entropy rather than 128 (for security buffer).
EDIT: I reviewed his code, and he's not hashing between calls to check the clock; the hash call itself causes the CPU to heat up in arbitrary ways which changes the timing between hashes and introduces more entropy; removing that call basically entirely defeats the idea behind the technique, these results are fully invalid.
---
I updated the code to insert the hash call, this is what I got for his original code on my machine, and the updated code with hashing on my machine (and the difference is cryptographically meaningful):
=== Original C — no hashing ===
Clock resolution: 0.000000001
Deltas (ns): 50 34 19 19 13 13 13 13 13 14 13 13 13 13 13 14 13 13 14 12 13 14 13 13 13 14 13 13 14 12 13 14 13 13 14 12 13 14 13 14 13 12 13 14 14 13 13 13 13
Deltas of deltas: -16 -15 0 -6 0 0 0 0 1 -1 0 0 0 0 1 -1 0 1 -2 1 1 -1 0 0 1 -1 0 1 -2 1 1 -1 0 1 -2 1 1 -1 1 -1 -1 1 1 0 -1 0 0 0
Maximum entropy: 90
=== C with SHA-256 between clock reads ===
Clock resolution: 0.000000001
Deltas (ns): 756852 1287 542 470 472 445 442 436 434 439 488 435 433 434 440 439 439 435 432 433 435 432 433 433 429 433 453 441 437 437 431 433 432 430 431 438 436 434 431 433 435 436 435 433 430 436 435 437 428
Deltas of deltas: -755565 -745 -72 2 -27 -3 -6 -2 5 49 -53 -2 1 6 -1 0 -4 -3 1 2 -3 1 0 -4 4 20 -12 -4 0 -6 2 -1 -2 1 7 -2 -2 -3 2 2 1 -1 -2 -3 6 -1 2 -9
Maximum entropy: 188Comment by sltkr 7 hours ago
Can you run the program 10 times and show me how much variance there actually is in the first column? Because if all the values lie between (say) 756000 and 757000 that's actually just 10 bits of entropy, not 19.5, and if the same applies to the other values, you're much closer to the original 90 bits.
Comment by Taek 2 hours ago
And here are the results of running that code:
=== No hashing ===
Clock resolution: 0.000000001 seconds
Clock reads: 500,000
Second-difference outcomes: 499,998
Retained outcomes: 449,998 (90.000%)
Average Shannon information: 1.755579 bits/retained outcome
Marginal min-entropy estimate: 1.339460 bits/retained outcome
Lag-1 conditional min-entropy: 0.960079 bits/retained adjacent outcome
Conservative descriptive proxy: 0.960079 bits/retained outcome
Proxy scaled per clock iteration: 0.864067 bits/iteration
These are empirical timing statistics, not a proven entropy rate.
=== One SHA-256 between clock reads ===
Clock resolution: 0.000000001 seconds
Clock reads: 500,000
Second-difference outcomes: 499,998
Retained outcomes: 449,998 (90.000%)
Average Shannon information: 4.205076 bits/retained outcome
Marginal min-entropy estimate: 3.610848 bits/retained outcome
Lag-1 conditional min-entropy: 3.351217 bits/retained adjacent outcome
Conservative descriptive proxy: 3.351217 bits/retained outcome
Proxy scaled per clock iteration: 3.016082 bits/iteration
These are empirical timing statistics, not a proven entropy rate.
------------As GPT helpfully points out, this isn't a proven guarantee, but a reasonable estimate is somewhere between 3 and 4 bits of entropy per hash. That means 50 is actually enough, though if you want to be conservative I don't think there's any harm in doing 500 or even 5,000 instead of 50. And, if you are going to be using this in a hostile environment, it doesn't hurt to also add a fortuna-like accumulator that resets your entropy every once in a while.
I said this in another reply as well, but the reason that you get 3-4 bits of entropy per hash is because of the fundamental nature of CPUs. In addition to having considerable professional experience with cryptography, I also have considerable professional experience with hardware; hardware is fickle as hell, especially when your transistors are tens of nanometers large. Every time you flip a bit, you expend some energy, which heats up the chip, and the heat changes the timing of the next clock cycle. Chips are composed of literally billions of transistors, and each one is going to have a different temperature, because clock cycles last less than a nanosecond (well, embedded hardware is slower but the same idea still applies reliably) and that's not enough time for temperature deltas to dissipate across the chip.
Hashing is particularly chaotic because it lights up a different set of transistors on each clock cycle, which means the hotspots on the chip are being jerked around. Some transistors are going to light up 5-10 times in a row, and others are going to be idle 5-10 times in a row, and then randomly that changes. And all of this changes the number of picoseconds that it takes for a clock cycle to complete, which means that each clock cycle is genuinely going to take a different amount of time to complete, and stuff like temperature throttling is completely not at play whatsoever, because we're not talking about chip-wide temperatures, we're literally talking about temperature deltas between transistor a and transistor b.
That makes it a really wonderful source of entropy for cryptographic applications, because the CPU clock is so critical that it's almost never buggy (especially relative to other components that provide entropy), it's also almost impossible to manipulate reliably by an attacker (unless the attacker has an exploit that allows them to set the value of the clock directly - which is possible, but it's a very narrow surface area relative to other entropy sources), and you can completely take advantage of this entropy entirely in userspace, which once again heavily minimizes attack surface area and exposure to bugs.
Comment by strenholme 9 hours ago
The point is this: Getting micro-timing won’t give us as much entropy as we want, but it will still give us entropy. So it’s a perfectly good yet-another-source of entropy to feed in to an entropy pool (such as the input to a XOF).
If those Coldcard devices had used this code as one source of entropy, and this source of entropy was the only entropy still working, they never would had been compromised.
(I won’t update my 18-year-old PRNG to use this code, of course, since that code is now 18 years old and there are no known weaknesses in said code)
Comment by Taek 8 hours ago
EDIT: I reviewed his code, and he's not hashing between calls to check the clock; the hash call itself causes the CPU to heat up in arbitrary ways which changes the timing between hashes and introduces more entropy; removing that call basically entirely defeats the idea behind the technique, these results are fully invalid.
Comment by strenholme 10 hours ago
The nice thing about using multiple entropy sources with a secure XOF is that the resulting entropy is at least as strong as the most secure entropy source given to the XOF.
Comment by Taek 9 hours ago
https://blog.cr.yp.to/20140205-entropy.html
TL;DR adding a compromised source of entropy to a pool of already secure sources of entropy can catastrophically compromise the final result.
It's better to source entropy from a smaller number of harder-to-compromise sources. That's why I like the iterated hashes method; the security surface area is both very small and highly likely to be well tested.
Comment by strenholme 9 hours ago
From that page:
>>>what I'm advocating here, for security reasons, is a sharp transition between
* before crypto: the whole system collecting enough entropy;
* after: the system using purely deterministic cryptography, never adding any more entropy.<<<
Which is exactly how a XOF should be used, and how I used the XOF in my code. A malicious source of entropy will need to perform 2^n operations to control n bits of the XOF’s output, and that’s assuming the malicious entropy source somehow perfectly knows the other entropy the XOF is using.
Comment by Taek 8 hours ago
The point here is to eliminate surface area for mistakes, and an XOF has a much larger and more complex implementation than iterated hashing against a timer.
Comment by Taek 3 hours ago
I am happy to have a discussion with you at the deepest technical levels of applied cryptography, this is not something I blindly made up on my own. I'm well studied in the field and can readily defend this technique.
Comment by 349ru3h4f03 12 hours ago
Comment by peri-cl 12 hours ago
[edit to add]: Also, the bulletin is solely about RDSEED zeros, whereas the OP is also reporting RDRAND zeroes.
Comment by ciupicri 11 hours ago
https://github.com/systemd/systemd/pull/12536/commits/1c53d4...
Comment by peri-cl 10 hours ago
I found the thread about it,
https://news.ycombinator.com/item?id=19848953
Yikes at this: "I am so glad I resisted pressure from engineers working at Intel to let /dev/random in Linux rely blindly on the output of the RDRAND instructure." -Theodore Ts'o (2013)
Comment by matja 10 hours ago
Comment by claudex 10 hours ago
Comment by 0x0 3 hours ago
Comment by CodesInChaos 12 hours ago
Comment by leonidasrup 11 hours ago
" I am so glad I resisted pressure from Intel engineers to let /dev/random rely only on the RDRAND instruction. To quote from the article below:
"By this year, the Sigint Enabling Project had found ways inside some of the encryption chips that scramble information for businesses and governments, either by working with chipmakers to insert back doors...."
Relying solely on the hardware random number generator which is using an implementation sealed inside a chip which is impossible to audit is a BAD idea. "
https://web.archive.org/web/20180611180213/https://plus.goog...
Putting a backdoor into CSPRNG is a favored way to break crypto, for example Dual_EC_DRBG.
"
Weaknesses in the cryptographic security of the algorithm were known and publicly criticised well before the algorithm became part of a formal standard endorsed by the ANSI, ISO, and formerly by the National Institute of Standards and Technology (NIST). One of the weaknesses publicly identified was the potential of the algorithm to harbour a cryptographic backdoor advantageous to those who know about it—the United States government's National Security Agency (NSA)—and no one else. In 2013, The New York Times reported that documents in their possession but never released to the public "appear to confirm" that the backdoor was real, and had been deliberately inserted by the NSA as part of its Bullrun decryption program. In December 2013, a Reuters news article alleged that in 2004, before NIST standardized Dual_EC_DRBG, NSA paid RSA Security $10 million in a secret deal to use Dual_EC_DRBG as the default in the RSA BSAFE cryptography library, which resulted in RSA Security becoming the most important distributor of the insecure algorithm. RSA responded that they "categorically deny" that they had ever knowingly colluded with the NSA to adopt an algorithm that was known to be flawed, but also stated, "We have never kept this relationship [with the NSA] a secret and in fact have openly publicized it."
"
Comment by matja 12 hours ago
Comment by rbanffy 11 hours ago
Anyone from the High-Performance Computing Center Stuttgart willing to play on the 720,320 Zen2 cores?
Comment by 20k 12 hours ago
Comment by repstosb 10 hours ago
Brooks talks about this in _Mythical_Man-Month_... if you really could "just implement the specification", then the specification itself would be complete enough to serve as your code. There will always be bugs in both.
Comment by vachina 10 hours ago
Comment by throwawayffffas 10 hours ago
Comment by bell-cot 10 hours ago
Comment by RicoElectrico 9 hours ago
Comment by hnacobsxph 12 hours ago
Comment by esafak 8 hours ago
Comment by rbanffy 11 hours ago
Looks like they tried 16-bit numbers. Does the odd behavior happen also on 32 and 64 (might take a long time to check - I'd start scratching my head after a couple hundred years of no zeroes) ones? Is the zero masking as some other fixed number, increasing its output count? Is RDRAND implemented as multiple reads of an internal state so that a larger random number takes longer?
Comment by wging 3 hours ago
> Yes, when using either 32-bit or 64-bit number, the lowest portion can output a zero, as I suggested on the attached example file as a modification to fix the problem. But a true zero (fitting the requested size), on AMD, never happens.
Another person (on page 2) confirms those results on an older AMD processor (but failed to reproduce on a very new one, 9950X3D).
Comment by corbinvachal 7 hours ago
Comment by lotus_uk 7 hours ago
Comment by Plainharbor21 12 hours ago
Comment by Ledgermellow 12 hours ago
Comment by fred_is_fred 7 hours ago
is an amusingly gross misunderstanding of what a C?O person does on a daily basis.
Comment by throwawayffffas 12 hours ago
Comment by gnfargbl 12 hours ago
By your argument, it would not be a problem if the RNG never generated 0. So, it must follow that it would also not be a problem if it never generated {1, 2, 3, ..., 253}.
That means that our RNG now only generates the values 254 and 255. Which of the values is generated is unpredictable on any given call. However, 7 of the 8 output bits are now always fixed and so completely predictable. Can you imagine how an attacker could exploit that?
Failing to generate only the number 0 is a weaker version of the same class of flaw.
Comment by brookst 11 hours ago
I don’t think you can rebut “you only lose one of many values” with “it’s the same as only having one left”.
Comment by gnfargbl 11 hours ago
If you want a casino example, then consider a roulette wheel that always lands on 36 but still pays out as usual. I think you'd want to play on it. Now consider one that always lands somewhere between 30 and 36. Still worth it, right? With careful bets and a good starting float you're still coming away from the table up (with a very high probability).
In fact for a roulette wheel you only need two dead pockets for the player to get an edge. Bias is exploitable.
Comment by brookst 9 hours ago
When the original point was that a tiny fractional loss in an RNG is not going to make a practical difference. Which I believe is also true. And it is also true that a large loss in an RNG is catastrophic.
They can both be true.
And roulette is 2 out of 38, 5.2%. That’s 17 times more than the 1/256 here, which was already a simplification of the (I think) 1/65536 in question.
Comment by gnfargbl 7 hours ago
> a tiny fractional loss in an RNG is not going to make a practical difference
I'm not so sure this is true. I don't think either of us is in a place to say whether this vulnerability has practical applications or not. A 1/65536 bias might seem like nothing important to you. It seems like potentially something to me, in a world where the attacker might control the volume of data generated.
Comment by BigTTYGothGF 9 hours ago
It certainly does not.
A never-zero RNG is something one should know about, so that it can be mitigated if necessary, but it's not inherently a dealbreaker.
Comment by throwawayffffas 11 hours ago
The bug has zero practical impact.
Comment by gnfargbl 11 hours ago
You could be correct that the very small bias here is not enough to be exploitable. But, given the history around this, it would be wrong to handwave it away as trivial.
Comment by kqp 9 hours ago
You can frame it around being “non predictable”, but then you need to define those words. It’s not, for example, a poker game where it’s trying to bluff you, right? It’s also not about just making predictions < 100% reliable and declaring victory. It must specifically make all predictions no better than random guessing, and that entails picking any number in range with equal probability, otherwise predictions like “it will be {hot spot}” or “it won’t be {cold spot}” do better than random chance. In this case, specifically, I can predict with 100% accuracy that the result won’t be 0, and that’s a flaw in its unpredictability. I can also predict a bunch of other things with slightly higher accuracy than random guessing, like that it will be odd or greater than max ÷ 2.
Comment by antiloper 12 hours ago
See section 7.3.17 of the Intel SDM, and how NIST SP800-90A (which the SDM refers to) defines "random number".
Comment by IAmBroom 10 hours ago
A weighted die is still random, but with an uneven distribution. This is effectively a 2^16-sided, weighted die.
Comment by throwawayffffas 10 hours ago
It's not a 2^16-sided weighted die. But a 2^16 - 1 sided fair die.
I am not saying there is no bug. I am saying the bug has no practical impact.
Sure if you are that one guy that is getting these values raw from the instruction and comparing to zero for some purpose then you are in trouble. But I am pretty sure no one is doing that, especially given that the bug surfaced after 6 years of millions of users.
Comment by necovek 8 hours ago
pick = rnrand16() - 0x7fff
if pick > 0...
where these are not equally likely anymore (I may have an off-by-one anyway ;)).Comment by throwawayffffas 38 minutes ago
Comment by swader999 11 hours ago
Comment by throwawayffffas 11 hours ago
Comment by swader999 10 hours ago
Comment by Hugsbox 11 hours ago
Comment by wat10000 10 hours ago
Comment by deadbabe 11 hours ago
Comment by flippingheck 10 hours ago
Comment by flexagoon 7 hours ago
Comment by dark-star 12 hours ago
Comment by adrian_b 10 hours ago
Otherwise, a slightly more complicated algorithm is necessary, where you reject a range of numbers either before computing the remainder (to make the set of possible values a multiple of the modulus) or after computing the value modulo some power of two (to reject values greater than your target).
Besides these 2 variants based on the remainder of division of integers, there are also 2 corresponding algorithms using multiplication of the input interpreted as a fraction, followed by taking the integer part of the result.
Comment by ExoticPearTree 12 hours ago
So it is not necessarily that it doesn't generate zero, they did not run enough times to increase the probability of actually generating a zero.
Comment by blensor 12 hours ago
You definitely would expect a roughly equal number of 0s as any other of those numbers since it's uniformly distributed. And definitely not 0
Comment by ExoticPearTree 11 hours ago
How would random numbers be uniformly distributed?
Comment by Hugsbox 10 hours ago
Comment by ExoticPearTree 3 hours ago
Comment by IAmBroom 7 hours ago
Think about the odds of a uranium atom decaying in a given second. Certainly a random event, yet for most seconds, the value is False, not True.
Comment by blensor 6 hours ago
If you have a random number generator your are relying on the fact that it is uniformly distributed and thus has no bias towards certain numbers. Or if it is not uniformly distributed you would want to know the exact distribution so you can correct for it.
If you have an RNG that is treated as putting out uniformly distributed numbers but it is does in fact favor some numbers over others, that would be a defect that can cause problems/be exploited.
Comment by BearOso 6 hours ago
Comment by thinkingQueen 10 hours ago
Comment by account42 10 hours ago
Comment by necovek 8 hours ago
With a few (say 10, so 655360 runs), you will not get a uniform distribution, and some numbers (like 0) might not appear.
Comment by zygentoma 12 hours ago
They also write:
> Running the same programs on an Intel processor, and the 0's are there with no problem.
Comment by matja 12 hours ago
Comment by throawayonthe 12 hours ago
Comment by m_antis89 11 hours ago
Comment by seanhunter 4 hours ago
Comment by flexagoon 7 hours ago
Comment by HackerThemAll 10 hours ago
Comment by BigTTYGothGF 9 hours ago
Comment by kevinwang 7 hours ago
> 32-bit XorShift should usually not be used to produce 32-bit numbers, because it only produces each number once, and never produces zero.
(From this page I found while trying to see if this was a common flaw in PRNGs: https://www.pcg-random.org/other-rngs.html )
Comment by ZiiS 12 hours ago
It is also possible that their code was generating too many zeros and the easiest fix was to discard them all.
Comment by jstanley 12 hours ago
I'm guessing you don't think there are people calling rdrand in a loop and throwing away the output with high probability except when it is 0, but I can't see how else you imagine people would be vastly more likely to use the output when it is 0?
Comment by ZiiS 11 hours ago
Comment by necovek 8 hours ago
Comment by ZiiS 8 hours ago
Comment by jstanley 9 hours ago
Comment by ZiiS 8 hours ago
Comment by jstanley 8 hours ago
Comment by dark-star 12 hours ago