-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Crystal::System::Random namespace #4450
Crystal::System::Random namespace #4450
Conversation
Just noting that /dev/urandom will give non-random data if entropy does not exists
|
src/random/system.cr
Outdated
class Random::System | ||
include Random | ||
|
||
def initialize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, otherwise Random::System.new
will call Random.new(seed = new_seed)
which won't work out.
Why |
@bararchy that is a cryptographic myth that kernel developers keep propagating. You cannot "run out" of entropy. |
e1330f1
to
a5b8d6e
Compare
a5b8d6e
to
e328ca9
Compare
@Sija because @bararchy see https://bugs.ruby-lang.org/issues/9569. The issue links to a bunch of documentation. |
@ysbaddaden @RX14 |
src/sys/linux/random.cr
Outdated
@@ -30,6 +30,21 @@ module Sys | |||
end | |||
end | |||
|
|||
# Returns a random UInt32. | |||
def self.random_number : UInt32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite arbitrary to consume the random source 4 bytes at a time... But it happens to be a good compromise between reading 1 byte at a time and having a proper implementation getting as many bytes as needed (see #3434).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somewhat. You avoid calling next_u
twice when generating a 64bits number but always read at least a next_u
(i.e. 4 bytes) when the maximum value fits.
Merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you understand that pull request.
This whole rand_type
business is already in the code base and already ensures that as few next_u
invocations are done as needed, it's not tied to the integer type but to the size of the requested range. Copying it here (without attribution, too) is not changing anything.
The thing that makes a difference is the granularity of next_u
which could return one byte instead of arbitrarily grabbing 4.
But then you start thinking about doing too many system calls, which my PR tries to mitigate with that random_bytes
thing which is introduced as an alternative to next_u
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I stupidly missed the next_u : UInt8
in your PR, and couldn't understand the granularity in next_u
you were talking about. I renamed random_number
to next_u
for symmetry with Random#next_u
and implementations now return an UInt8, except OpenBSD which calls arc4random
and thus returns an UInt32.
spec/std/random/system_spec.cr
Outdated
rng = Random::System.new | ||
|
||
it "returns random numbers from the secure system source" do | ||
typeof(rng.next_u).should eq(UInt32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation as presented in this PR happens to have next_u
returning an UInt32
but I don't think it should be part of the specification. module Random
does not require next_u
to return an UInt32
, anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong assumption on my side. I was lured by arc4random, libsodium's sysrandom, our ISAAC and MT19937 implementations all returning an UInt32. I see the ISAAC64 algorithm returns an UInt64, so as long as Random
handles any size, I'll remove the spec limitation.
|
||
describe "Random::System" do | ||
rng = Random::System.new | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth testing one nontrivial case, like generating a 64 bit signed integer. It is not part of Random::System
directly but having this called in real code can be better at pointing the compiler at something that accidentally went wrong.
src/random/system.cr
Outdated
# `getrandom` (if the kernel supports it) and fallbacks on reading from | ||
# `/dev/urandom` on UNIX systems. | ||
class Random::System | ||
include Random |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not this:
module Random::System
extend Random
I think it would look awesome!
There is no point in creating an instance of an object without state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both Random::ISAAC
and Random::MT1993
merely include Random
and class methods seem to be specific to Random
. What would extend
bring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use Random::System
instead of Random::System.new
. It can act as an instance of Random
for all purposes, but without a need to have an object on the heap.
#3434 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about making it a struct instead? I'd like PRNG implementations to follow the same design, but there is no context, so a zero-sized struct should fit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this would break any design. It's already broken because the RNG does not have initialization with a seed (and there's no reason to require RNGs to do that). If you're dropping the (seed)
part from .new(seed)
, might as well drop the whole thing.
This way we can at least have Random::DEFAULT
and Random::System
be interchangable. It's just not nice to have to create these fake instances. Makes no sense to keep one and makes no sense to create one every time.
I'm really keen to showcase the power of the language this way, and also emphasise that Crystal does not keep state regarding this RNG.
@ysbaddaden @Sija I like |
I'd very much prefer platform as it is more descriptive and having |
@ysbaddaden I'd vote for @bcardiff proposition of using |
1c417e9
to
7f8e157
Compare
src/sys/unix/random.cr
Outdated
@@ -17,5 +17,15 @@ module Sys | |||
raise "Failed to access secure source to generate random bytes!" | |||
end | |||
end | |||
|
|||
def self.random_number : UInt32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically the main problem I have with this PR is this method arbitrarily getting 4 bytes and being called just random_number
.
7f8e157
to
8f7a78f
Compare
Looks awesome now! Thanks for bearing with me. I'll keep an eye on this -- and keep arguing about making |
Extracts the platform specific parts out of SecureRandom into a simple API in `sys/random` that should be implemented for each platform.
Allows to generate random numbers using a secure source provided by the system. It actually uses the same source as SecureRandom. Includes changes by Oleh Prypin (@prypin) to try and read as few bytes are required from `/dev/urandom`.
8f7a78f
to
704a05b
Compare
I moved everything to the |
I still think that platform describes the contents of the namespace better. But its pretty arbitrary so I won't push it. |
I'm ok with @ysbaddaden note that we have now the |
|
I started adding the the good:
the less good:
the bad:
|
@ysbaddaden For me the best part of skip file is that it allows the globs to keep working in any directory. There could be other solutions for this problem: like ignoring in require globs files prefixed with |
@ysbaddaden I don't quite understand what you mean by "it duplicates the dispatch in the main file". Surely you'd just require all the relevant files and let And as for the default file, I think there being an entirely cross-platform fallback will be quite rare, so it's best to have a whitelist in all files, (we know the entire set of targets). In that case, adding a new system requires only updating the file of the implementation for that system to add the system to the whitelist. |
@RX14 , he probably meant that in linux version would say Later on, for example in windows, the windows file will say Having some chained But I do value more the |
@bcardiff I was suggesting simply not using |
Does a glob of @RX14 defaults won't be that rare. Quite often we'll have "if windows else unix" and not much more. NetBSD or another UNIX system, for example, would require to put a bunch of |
@ysbaddaden How about adding a |
My concern with the glob is mainly consistency. The lib_c case is not the same since the path is selected from the triplet there. If the feature of this PR is fine let's move forward as is. There other PR willing to move things to Crystal::System and wit should be used for windows. Crystal::System should not be used directly so it's almost fine if globs are not supported maybe. Things can be improved later. |
Is this really the place to discuss all of this? |
@oprypin sorry, you may click @RX14 an |
@ysbaddaden I guess a "cover file" which just uses macros to require the correct file is a better solution than |
Eventually Crystal::System might have more things in the hierarchy / directory structure. File in @ysbaddaden, if you don't like it (even with other folder name), no worries and feel free to merge this. So we can start moving other system specific code :-) |
@bcardiff Rather then creating a messy and confusing folder layout how about just not using |
I thought a little more about this and I'll add the Maybe I can add the |
Protects against glob requires of `crystal/system/**` that would fail, since it would load conflicting or incompatible implementations.
This is automatically set based on the target triple for known unix systems. This should eventually simplify skip_file conditions for generic UNIX implementations.
Introduces a
Crystal::System::Random
namespace proposal to abstract the platform specifics of generating random numbers, using a secure random source. This follows and examplifies what is proposed in #4406.The API is limited to two methods that must be implemented for a platform:
Crystal::System::Random.random_bytes(buf : Bytes) : Nil
to fill a Slice with random bytes;Crystal::System::Random.next_u : UIntX
to return an unsigned random number (from an UInt8 to an UInt64).The implementation has been extracted from
SecureRandom
. It usesgetrandom
on linux (if supported by the kernel) or reads from/dev/urandom
on other POSIX systems. In addition OpenBSD now usesarc4random
.This patch also introduces a new
Random::System
class, which permits to use a secure system source for generating random numbers, instead of a PRNG. This includes patches taken from @oprypin in #3434In addition, maybe
Random.new_seed
should now useCrystal::System::Random
instead of using a pseudo-random (if not deterministic) value?