dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.09k stars 1.56k forks source link

Provide a way to generate psuedo & crypto random byte slice #53339

Open rajveermalviya opened 1 year ago

rajveermalviya commented 1 year ago

Currently the Random class doesn't provide a way to get a randomly generated raw bytes. It is especially useful when generating a random string or in other cases where multiple calls to the secure random generator for each byte is not desirable.

Many runtimes provides the same like:

jakemac53 commented 1 year ago

cc @lrhn

rajveermalviya commented 1 year ago

(I will send a patch soon!)

natebosch commented 1 year ago

It would be breaking to add it to Random. We could add it as an extension method.

extension RandomExtensions on Random {
  int get nextByte => nextInt(256);

  Uint8List nextBytes(int length) {
    final result = Uint8List(length);
    for (var i=0; i<length; i++) {
      result[i] = nextByte;
    }
    return result;
  }
}
rajveermalviya commented 1 year ago

@natebosch, I see that the example you provided uses Uint8List - does that mean it's okay for dart:math to expose a type from dart:typed_data ?

jakemac53 commented 1 year ago

If we did the extension, we would want to consider just defining it inside of dart:typed_data.

That is also potentially breaking though, I wouldn't be that surprised if somebody already has an implementation such as the one suggested here, defined in their own project or even a pub package.

lrhn commented 1 year ago

The problem with generating random bytes, plural, is that the default random generator doesn't have that much entropy, so creating 256 "random bytes" will not generate more than about 8-16 bytes of entropy anyway.

If you use a cryptographic random number generator, it's still problematic to use a lot of entropy at once, you may still exhaust it's entropy pool, which can take time to refill.

rajveermalviya commented 1 year ago

(disclaimer: I am not a cryptography expert)

It's true, exhaustion of the entropy pool is bad - but If i am not wrong, doesn't current API also have the same problem?

final rand = Random.secure();
List.generate(1024, (_) => rand.nextInt(256), growable: false); // This could still lead to exhaustion

In case of Random.secure it's worse because exhaustion can stall the process & also stall other processes that want to use the crypto random generator, until the entropy pool is filled, essentially Denial-Of-Service.

So here It's developers responsibility to be nice and ask for small amount of bytes & less amount of times. Don't know what really can be done here.

lrhn commented 1 year ago

On the web, you can use getRandomBytes of using the secure Random generator, and we can do something similar natively.

Using plain Random, I guess we can document it as just doing a sequence of nextInt operations with an undefined size. This should ensure that providing the same random object, with the same seeded state, gives the same result. Or we can use the same generator as getRandomValues, and use the original Random for the seed.

And as you say, it's up to the user to not assume more entropy than can be provided. We just need to document that.

julemand101 commented 1 year ago

If you use a cryptographic random number generator, it's still problematic to use a lot of entropy at once, you may still exhaust it's entropy pool, which can take time to refill.

On what platforms is this a problem? I can see on both Android, Linux and Mac OS, Dart uses /dev/urandom as device for getting random numbers where urandom are documented to never block:

When read, the /dev/urandom device returns random bytes using a pseudorandom number generator seeded from the entropy pool. Reads from this device do not block (i.e., the CPU is not yielded), but can incur an appreciable delay when requesting large amounts of data.

If we talks about the "appreciable delay" then sure. But it is not really a question of using "a lot of entropy" since we are not using it up. Especially modern "urandom" are really fast today.

But could not find anything about how the Windows API is working so could be the Windows platform there is the problem child?

(Another note. I wounder if it would make sense to change the Linux implementation to instead of opening the character device /dev/urandom to instead use the getrandom() system call. As far I understand, it is the more "modern" way to get random numbers on Linux platforms today).

rajveermalviya commented 1 year ago

Created CL https://dart-review.googlesource.com/c/sdk/+/322861

inetic commented 7 months ago

Sorry to be a bit off topic, but the examples above incorrectly use rand.nextInt(255) instead of rand.nextInt(256). This github issue is one of the first when searching for "generate random Uint8List" so I though it might be worth correcting it. The documentation for Random's nextInt is a bit confusing in that is uses the max as the name of the argument which usually means inclusive, but is actually exclusive.

Here is an example from the dart source code:

final kTokenByteSize = 8;
Uint8List bytes = Uint8List(kTokenByteSize);
Random random = Random.secure();
for (int i = 0; i < kTokenByteSize; i++) {
  bytes[i] = random.nextInt(256);
}