Merge SecureRandom into Random and Random::Secure#4894
Merge SecureRandom into Random and Random::Secure#4894ysbaddaden merged 2 commits intocrystal-lang:masterfrom
Conversation
|
Looks like the formatter failed. |
d1e9d82 to
af8393f
Compare
|
Indeed. I pushed again. |
|
I'm not sure if it makes much sense to split this change into two commits. Future readers will just get confused by a commit that adds a bunch of stuff and a commit that deletes a bunch of stuff, when really it's mostly being moved. |
|
Having 2 commits may help reviewing each patch, avoiding remove/renamings noise. Yet, I agree we should "squash and merge" into a single one if we merge this. |
| # Random.new.random_bytes(slice) | ||
| # slice # => [217, 118, 38, 196] | ||
| # ``` | ||
| def random_bytes(buf : Bytes) : Nil |
There was a problem hiding this comment.
The one method I added is this one: a generic random_bytes(buf) that depends on next_u size, to consume as few numbers as possibles from the PRNG.
I'm not very confident in the filling remaining bytes part (when buf.size isn't a multiple of sizeof(next_u)), and would like some review.
There was a problem hiding this comment.
I don't think, it is endianness really matters for 'random_bytes'.
And with this assumption, your 'tail filling' is perfectly correct.
Note, some PRNG can have more efficient implementation of 'random_bytes'.
Chacha20 generates 64 bytes at once, for example.
May be it is better to check 'respond_to?(:random_bytes)' first?
There was a problem hiding this comment.
:-( I see: method should be called differently. random_bytes_native?
There was a problem hiding this comment.
This code is in the base Random class. PRNGs are free to override random_bytes, the only bad thing is that they'd have to reimplement the buffer filling logic.
There was a problem hiding this comment.
@asterite, but were you reviewing the old code that these comments are on, or the new code?
There was a problem hiding this comment.
@oprypin I only see one code, the same I see when I check out the code:
def random_bytes(buf : Bytes) : Nil
ptr = buf.to_unsafe
finish = buf.to_unsafe + buf.size
while ptr < finish
random = next_u
rand_ptr = pointerof(random).as(UInt8*)
if IO::ByteFormat::SystemEndian != IO::ByteFormat::LittleEndian
rand_ptr.to_slice(sizeof(typeof(next_u))).reverse!
end
rand_ptr.copy_to(ptr, {finish - ptr, sizeof(typeof(next_u))}.min)
ptr += sizeof(typeof(next_u))
end
end|
Linux 32-bit crashed again on Travis? |
RX14
left a comment
There was a problem hiding this comment.
I don't mind this being rebased instead of squashed. I like having an explanatory commit log.
src/random.cr
Outdated
| # ``` | ||
| def random_bytes(buf : Bytes) : Nil | ||
| n = buf.size / sizeof(typeof(next_u)) | ||
| remaining = buf.size - n * sizeof(typeof(next_u)) |
There was a problem hiding this comment.
Couldn't this be replaced by divmod?
src/random.cr
Outdated
| remaining.times do |i| | ||
| bits = i * 8 | ||
| mask = typeof(next_u).new(0xff << bits) | ||
| buf[-i - 1] = UInt8.new((bytes & mask) >> bits) |
There was a problem hiding this comment.
The -i - 1 is cryptic. It needs a note at least.
I'd prefer to go for the simple method of creating a base = n * sizeof(typeof(next_u)) and using base + i to index.
| # | ||
| # It is recommended to use the secure `Random::System` as a source or another | ||
| # cryptographically quality PRNG such as `Random::ISAAC` or ChaCha20. | ||
| def uuid : String |
There was a problem hiding this comment.
I'd like UUID to be moved into it's own struct with methods before 1.0 so hopefully this method should disappear and be replaced by a constructor on that. Doesn't help with the hex/base64 methods though. I don't think that using Random::DEFAULT.hex will be a world-ending issue in Crystal anyway (not that it's usually the correct thing to do) because PCG32 has fairly strong guarantees.
| @@ -1,5 +1,3 @@ | |||
| require "secure_random" | |||
src/random.cr
Outdated
| remaining = buf.size - n * sizeof(typeof(next_u)) | ||
|
|
||
| slice = buf.to_unsafe.as(typeof(next_u)*).to_slice(n) | ||
| slice.each_index { |i| slice[i] = next_u } |
There was a problem hiding this comment.
This gives different results depending on endianness. May be worth using IO::ByteFormat::LittleEndian
There was a problem hiding this comment.
Also seems like the last few bytes are big-endian while the rest are (on most systems) little endian.
I'm suggesting a different implementation in ysbaddaden#2
spec/std/random_spec.cr
Outdated
| bytes = Bytes.new(2000) | ||
| TestRNG.new(RNG_DATA_32).random_bytes(bytes) | ||
| bytes.size.should eq 2000 | ||
| bytes[1990, 10].should_not eq(Bytes.new(10)) |
There was a problem hiding this comment.
These specs don't really check anything.
Needs something like https://github.com/crystal-lang/crystal/pull/3434/files#diff-0e9f78d10caafce0823cedec53f1c6f0
I'm suggesting an implementation in ysbaddaden#3
|
Is this Pull request breaks seed code on #4675? |
|
Probably it will break, if will be merged before. |
|
Thanks for accepting my changes! As a note, if these commits are squashed during merge, authorship info will be lost. So better apply whatever level of squashing is wanted manually. |
|
I like this, but why not call it |
|
I kept the If others agree, I'll change the naming, I shall change the specs to expect a fixed result (for |
|
If someone could review @oprypin's rewrite of the generic |
| random = next_u | ||
| rand_ptr = pointerof(random).as(UInt8*) | ||
| if IO::ByteFormat::SystemEndian != IO::ByteFormat::LittleEndian | ||
| rand_ptr.to_slice(sizeof(typeof(next_u))).reverse! |
There was a problem hiding this comment.
Would it be possible to save result of typeof(next_u) call into a variable? In this way we wouldn't need to call it 3 more times.
There was a problem hiding this comment.
These are not actual calls, it's a fully compile-time construct. Using a variable could prevent some compiler optimizations.
This is pure speculation though.
| while ptr < finish | ||
| random = next_u | ||
| rand_ptr = pointerof(random).as(UInt8*) | ||
| if IO::ByteFormat::SystemEndian != IO::ByteFormat::LittleEndian |
There was a problem hiding this comment.
Is it needed for anything except tests?
There was a problem hiding this comment.
This is needed to produce the same results for the same seed, on different systems.
Note that this code is NOT only used for Random::System
|
I just suggest using |
|
I think providing the system RNG is pointless if it's not secure (i.e. we can do better in userspace), so we should call it |
|
@ysbaddaden, it seems like the rename to |
SecureRandom has been dropped in favor to Random::Secure (renamed from Random::System) which is now the secure source for random numbers suitable for cryptography. Moves SecureRandom#random_bytes(Bytes) into Random::Secure, and implements a generic Random#random_bytes(Bytes) to fill a Bytes with random numbers from any PRNG. Moves SecureRandom methods such as #base64 and #uuid into Random, so any PRNG implementation may use these methods; they should only be used with a secure PRNG thought (except maybe in test suites). This change allows to swap PRNG at will. For example swap Random::Secure for Random::ISAAC (seeded from Random::Secure), or use a cryptographically secure but slow PRNG in production, and an insecure but fast and determinable PRNG in test suites, for example.
e5394d7 to
79d0358
Compare
|
All done. Waiting for CI to run. |
As per #4882 (comment) keeping
SecureRandomis a source of duplication and of confusion. Despite the explicit naming, there are contradictory understandings as what should be the source for secure random numbers in different contexts:SecureRandom,Random::SystemorCrystal::System::Random?In order to avoid this pitfall, I propose to clarify that:
Random::Secureis the source for cryptographically secure random numbers (renamingRendom::System, see discussion below);Crystal::System::Randomis an internal abstraction to feedRandom::System(i.e. nothing else must use it).SecureRandomshall... be removed, and its usage replaced withRandom::Systemthroughout stdlib.Following these, this patch:
#base64,#urlsafe_base64and#uuidmethods fromSecureRandomintoRandom;Random#random_bytesmethods;Random::SystemasRandom::Secure;#random_bytes(buf)method fromSecureRandomintoRandom::Secure;SecureRandomandCrystal::System::Randomusages in stdlib to their equivalent calls onRandom::Secure.A benefit is that all PRNG implementations may benefit from
#urlsafe_base64or#uuidfor example, but these methods should only be used with a cryptographically secure PRNG only (e.g.Random::Secure,Random::ISAAC, ChaCha20, ...). This is stated obviously in the documentation ofRandomand each concerned method.That being said, using an insecure but very fast and deterministic PRNG may be suitable in test suites, which is now possible, just by switching the PRNG. Switching from the system source, using a cryptographically secure PRNG (correctly seeded from the system source) is now also possible.