There's some test code in go that uses /dev/urandom, but this doesn't
seem like the best cross-platform approach.
From my "99 Bottles" code: http://gopaste.org/view/MunWk
// Create a randomized list over the passed range (end-inclusive)
func randList(min, max int) []int {
if max < min {
min, max = max, min
}
length := max - min + 1
rand.Seed(time.Nanoseconds())
list := rand.Perm(length)
for index, _ := range list {
list[index] += min
}
return list
}
While the above is not what you asked for, it should be simple to modify to get other kinds of random content into other numeric types.
-BobC
From my "99 Bottles" code: http://gopaste.org/view/MunWkOn 12/19/2009 11:16 PM, Michael Hoisie wrote:
Does go have a way to generate a random byte array? I can't seem to
find it in the rand package. This would be useful for generating
unique identifiers (UUID), or for things like encryption keys or
password salts.
There's some test code in go that uses /dev/urandom, but this doesn't
seem like the best cross-platform approach.
// Create a randomized list over the passed range (end-inclusive)
func randList(min, max int) []int {
if max < min {
min, max = max, min
}
length := max - min + 1
rand.Seed(time.Nanoseconds())
It is *absolutely* required in order to obtain different behavior on each run. Otherwise, using the same default seed over and over, you get exactly the same random number sequence every time.
I needed this so that I could obtain useful statistics for program performance over many runs. If the runs are all the same (which is what happens if the random number generator is not seeded for each run), then by definition you have no statistics!
After only 12 runs, the expected performance, that randomization produces a number of misses that was expected to be half the worst-case, was proven with 90% confidence.
Failing to properly seed the random number generator before each run is a typical freshman CS error when first working with stochastic processes. If you were to generate crypto keys or salts without reseeding the generator, your crypto system would be fatally weakened.
Here's a simple program that clearly illustrates the issue (rand_test.go):
package main
import (
"fmt"
"rand"
"time"
)
func main() {
fmt.Printf("First value from random number generator = %v\n", rand.Int63())
rand.Seed(time.Nanoseconds())
fmt.Printf("First value from re-seeded generator = %v\n", rand.Int63())
}
Compile this program, then run it several times. On my system, I got the following:
[bobc@snafu Go]$ rand_test.out
First value from random number generator = 5577006791947779410
First value from re-seeded generator = 2561830037514801697
[bobc@snafu Go]$ rand_test.out
First value from random number generator = 5577006791947779410
First value from re-seeded generator = 497439534987103448
[bobc@snafu Go]$ rand_test.out
First value from random number generator = 5577006791947779410
First value from re-seeded generator = 1179316969969921209
[bobc@snafu Go]$ rand_test.out
First value from random number generator = 5577006791947779410
First value from re-seeded generator = 2151590588581465432
Notice that the first number is ALWAYS THE SAME on every run, and the next value, after re-seeding with a different seed every time, is ALWAYS DIFFERENT! Only with proper re-seeding can you get something that appears random.
Which would you want to use for generating crypto keys?
I believe it is a serious mistake for any rand package to have the same default behavior each time it is used, as is the case with the "convenience" generator in Go's rand package. If you *do* want the same sequence of numbers over and over, then it is far better to force the programmer to have to manually initialize the generator using a constant seed.
In the file rand.go (in package rand), Go's "convenience" random number generator is hard-wired with an initialization seed value of 1 (one).
My selection of time.Nanoseconds() as my source of a random-ish seed isn't theoretically ideal, but it is the best source of changing numbers that is trivially easy to access. It is effectively impossible that I will *ever* get the same seed value twice, which is all I really care about for the random number sequences I need.
-BobC
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab
He was referring to reseeding with every call to the function instead
of doing it once at startup, or using once.Do(...).
True!
Since the list function gets called only once, I got lazy and chose to keep all the rand code together (it was added late in development). The code has been updated: http://gopaste.org/view/9X6w7
As a specific risk, it would become detectable only if the function were called many times with a periodicity that is stable to within nanoseconds (a "timing attack").
The defense for a timing attack is classic: Destroy the ability to repeat execution in the time domain by adding a small random delay before the random number generator returns. With such a defensive generator, even my lazy code should be safe.
-BobC
You should not rely on that. It's enough for a library to _use_ that
global random generator to disallow you from getting your sequence. To
get the same sequence, you need some local generator anyway.
> As a specific risk, it would become detectable only if the function
> were called many times with a periodicity that is stable to within
> nanoseconds (a "timing attack").
It's worse than that. Seeding a random number generator is very hard
because it's difficult to generate enough noise to fill the generator
given only a few input bits. In my experience, a random number
generator is much worse right after seeding that after it's run a few
million cycles. Also, if you seed it with something predictable like
the time, the risk is obvious. Seeding it on every call is folly.
-rob
Is this true? I thought an RNG was just a function that generated a,
hopefully, very long sequence of numbers that appeared random. It
didn't matter where in the sequence you started since the sequence was
cyclic. By setting a seed you were specifying where in the sequence
you were starting. This way you could generate the same sequence over
and over. Since it is just a sequence the RNG should behave the same
irregardless of whether you are taking a sample near where it was
seeded or a few million cycles later.
Or am I just behind the times and newer RNGs behave differently?
> Also, if you seed it with something predictable like
> the time, the risk is obvious. Seeding it on every call is folly.
This I agree with.
geoff
http://www.gnu.org/software/gsl/manual/html_node/Other-random-number-generators.html
gsl_rng_ranf - seed is lower 32 bits
Anyway, even if you can use different seed to get the same sequence
that you would have got by just cycling a bit, it's clear that after
those cycles, this new seed is less predictable from initial
conditions - for example, if input is time, then having the seed
directly refer to some moment in time is worse than if it's already
randomized. It means - the operation from seed to 3rd number is not
seed+3, but something you can't predict by such easy arithmetics.
Thus, doing those cycles is worth doing because it gives you more
random seed than the inital value you got from time or somewhere. As
we are talking about pseudorandom numbers, anything you can do to make
it harder to get back to seed is good. Having it tick after each
seconds after getting a time-based seed gives you a rough equivalent
of non-time-based-seed, which is much larger range. Also, if seed only
seeds some 64 bits of value, but the value itself is larger, it's also
a way to get to those other values. So it clearly makes sense to not
rely on this first number, unless it's a true random in itself.
Just to make the point more clear.
Lets suppose you have the following seed:
const xor time();
Const is some constant known to you, time() is current time. Lets
suppose cracker already knows the const, thus lets see time():
Current time is made from two parts - constant and variable. Variable
is last x bits, constant is the beginning (for example, right now the
first bits, which are the same for 1970.1.1 and today are constant;
the rest is more or less variable).
Thus, you have some not very long variable as seed value.
Lets take an example of 256 possibilities. You have, at first, 4 bits
more or less random as initial condition - this is the time,
simplified - and then you have those other 4 bits. You have an
operation: seed+=16; which moves you to the next seed. You actually
don't have other operation to get access to those other numbers - time
is very limited and as limited are most other things, which give you
some initial seed. They all represent the first 4 bits here. Now, each
time you calculate a random number, you do seed+=16.
When hacker knows the range of initial condition - these 4 bits, he
has to try 16 possibilities. If you have randomly done 0, 1 or 2 times
of getting next number, the range becomes 16*3. If you have randomly
done 5, 7 or 8 times of it, the range becomes yet larger unless
cracker knows that those jumps are 5, 7 or 8. If you simply add 0, 1
or 2 to your seed after getting your first 4 bits, you will make a
range only 18. Given that access to original number needs iterating
over the range and doing some more or less complex operation for each
iteration, your task is to get this range as big as possible. Also,
given that adding 16 is a bit harder to do than adding 1 (you have to
generate a new number in the real operation presented here as +16) it
makes sense over just adding to the seed even if seed is only single
number representing the whole state of a generator at any time.
As 64-bit seed itself has a range of 2**64, doing few million
iterations is meaningful - if x is initial range, then each iteration
adds new x to range. And in this case, even if you do some adding or
multiplying with initial seed, those iterations are still
qualitatively different from those operations.
for i := time(); i > 0; i-- {
seed(i);
if correctSolution() { // Uses current seed
break;
}
}
Given that one has possibly done seed += timerange*int, you need more
iterating.
Now, given that up to 1000000 additional iterations are done, consider
the following:
for i := time(); i > 0; i-- {
seed(i);
for j := 0; j < 1000000; j++ {
pushseed();
if correctSolution() {
break;
}
popseed();
random(); // Calculate next number
}
}
You can restructure the latter, but it's clear that it's more complex
in all cases.
Given that one has possibly done seed += timerange*int with the latter
case, you need once again more iterating, but you still need this
iterator, which calculates the next number.
Thus, doing whatever you can is the best ;)
No, not yet.
Russ