Skip to content

Commit

Permalink
Add Random::System
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
ysbaddaden committed May 25, 2017
1 parent 0c6135b commit 704a05b
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 9 deletions.
16 changes: 16 additions & 0 deletions spec/std/random/system_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "spec"
require "random/system"

describe "Random::System" do
rng = Random::System.new

it "returns random number from the secure system source" do
rng.next_u.should be_a(Int::Unsigned)

x = rng.rand(123456...654321)
x.should be >= 123456
x.should be < 654321

rng.rand(Int64::MAX / 2).should be <= (Int64::MAX / 2)
end
end
7 changes: 7 additions & 0 deletions src/crystal/system/random.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ module Crystal
module Random
# Fills *buffer* with random bytes from a secure source.
# def self.random_bytes(buffer : Bytes) : Nil

# Returns a random unsigned integer from a secure source. Implementations
# may choose the integer size to return based on what the system source
# provides. They may choose to return a single byte (UInt8) in which case
# `::Random` will prefer `#random_bytes` to read as many bytes as required
# at once, avoiding multiple reads or reading too many bytes.
# def self.next_u
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions src/crystal/system/unix/arc4random.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ module Crystal::System::Random
def self.random_bytes(buffer : Bytes) : Nil
LibC.arc4random_buf(buffer.to_unsafe.as(Void*), buffer.size)
end

def self.next_u : UInt32
LibC.arc4random
end
end
14 changes: 14 additions & 0 deletions src/crystal/system/unix/getrandom.cr
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ module Crystal::System::Random
end
end

def self.next_u : UInt8
init unless @@initialized

if @@getrandom_available
buf = uninitialized UInt8[1]
getrandom(buf.to_slice)
buf.to_unsafe.as(UInt8*).value
elsif urandom = @@urandom
urandom.read_byte.not_nil!
else
raise "Failed to access secure source to generate random bytes!"
end
end

# Reads n random bytes using the Linux `getrandom(2)` syscall.
private def self.getrandom(buf)
# getrandom(2) may only read up to 256 bytes at once without being
Expand Down
10 changes: 10 additions & 0 deletions src/crystal/system/unix/urandom.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,14 @@ module Crystal::System::Random
raise "Failed to access secure source to generate random bytes!"
end
end

def self.next_u : UInt8
init unless @@initialized

if urandom = @@urandom
urandom.read_bytes(UInt8)
else
raise "Failed to access secure source to generate random bytes!"
end
end
end
1 change: 1 addition & 0 deletions src/lib_c/amd64-unknown-openbsd/c/stdlib.cr
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ lib LibC
rem : Int
end

fun arc4random : UInt32
fun arc4random_buf(x0 : Void*, x1 : SizeT) : Void
fun atof(x0 : Char*) : Double
fun div(x0 : Int, x1 : Int) : DivT
Expand Down
11 changes: 2 additions & 9 deletions src/random.cr
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,7 @@ module Random
end

loop do
# Build up the number combining multiple outputs from the RNG.
result = {{utype}}.new(next_u)
(needed_parts - 1).times do
result <<= sizeof(typeof(next_u))*8
result |= {{utype}}.new(next_u)
end
result = rand_type({{utype}}, needed_parts)

# For a uniform distribution we may need to throw away some numbers.
if result < limit || limit == 0
Expand Down Expand Up @@ -228,9 +223,7 @@ module Random
end

# Generates a random integer in range `{{type}}::MIN..{{type}}::MAX`.
private def rand_type(type : {{type}}.class) : {{type}}
needed_parts = {{size/8}} / sizeof(typeof(next_u))

private def rand_type(type : {{type}}.class, needed_parts = sizeof({{type}}) / sizeof(typeof(next_u))) : {{type}}
# Build up the number combining multiple outputs from the RNG.
result = {{utype}}.new(next_u)
(needed_parts - 1).times do
Expand Down
52 changes: 52 additions & 0 deletions src/random/system.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require "crystal/system/random"

# Generates random numbers from a secure source of the system.
#
# For example `arc4random` is used on OpenBSD, whereas on Linux it uses
# `getrandom` (if the kernel supports it) and fallbacks on reading from
# `/dev/urandom` on UNIX systems.
struct Random::System
include Random

def initialize
end

def next_u
Crystal::System::Random.next_u
end

{% for type in [UInt8, UInt16, UInt32, UInt64] %}
# Generates a random integer of a given type. The number of bytes to
# generate can be limited; by default it will generate as many bytes as
# needed to fill the integer size.
private def rand_type(type : {{type}}.class, needed_parts = nil) : {{type}}
needed_bytes =
if needed_parts
needed_parts * sizeof(typeof(next_u))
else
sizeof({{type}})
end

buf = uninitialized UInt8[sizeof({{type}})]

if needed_bytes < sizeof({{type}})
bytes = Slice.new(buf.to_unsafe, needed_bytes)
Crystal::System::Random.random_bytes(bytes)

bytes.reduce({{type}}.new(0)) do |result, byte|
(result << 8) | byte
end
else
Crystal::System::Random.random_bytes(buf.to_slice)
buf.to_unsafe.as({{type}}*).value
end
end
{% end %}

{% for type in [Int8, Int16, Int32, Int64] %}
private def rand_type(type : {{type}}.class, needed_bytes = sizeof({{type}})) : {{type}}
result = rand_type({{"U#{type}".id}}, needed_bytes)
{{type}}.new(result)
end
{% end %}
end

0 comments on commit 704a05b

Please sign in to comment.