Remix.run Logo
Taek an hour ago

You can effectively achieve the same result with this simple operation:

  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.

strenholme 31 minutes ago | parent | next [-]

I wouldn’t trust it as a sole source of entropy, but it can be one of multiple entropy sources to feed in to an XOF to get secure numbers.

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.

sltkr 40 minutes ago | parent | prev [-]

This comment demonstrates everything that's wrong with people trying to be clever and rolling their own crypto.

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();
alerighi 17 minutes ago | parent | next [-]

Depends in what trust do you have over your hardware/OS. If you assume the hardware is potentially backdoored, and the OS is proprietary, or even if open could have malware/rootkits that can thinker around the random number generator, the solution of using a sole implementation inside the program (assuming the sha256 function is inside the program itself) maybe better.

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).

sltkr 8 minutes ago | parent | prev [-]

And to show my objections are not just theoretical I wrote a little program to check:

    #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.