diff --git a/util/random/rand.go b/util/random/rand.go index 0fcee6fed..b7e9a7e7c 100644 --- a/util/random/rand.go +++ b/util/random/rand.go @@ -3,9 +3,14 @@ package random import ( + "bytes" "crypto/cipher" "crypto/rand" + "crypto/sha256" + "io" "math/big" + + "go.dedis.ch/kyber/v3/xof/blake2xb" ) // Bits chooses a uniform random BigInt with a given maximum BitLen. @@ -45,34 +50,53 @@ func Bytes(b []byte, rand cipher.Stream) { } type randstream struct { + Readers []io.Reader } func (r *randstream) XORKeyStream(dst, src []byte) { - // This function works only on local data, so it is - // safe against race conditions, as long as crypto/rand - // is as well. (It is.) l := len(dst) if len(src) != l { panic("XORKeyStream: mismatched buffer lengths") } - buf := make([]byte, l) - n, err := rand.Read(buf) - if err != nil { - panic(err) - } - if n < len(buf) { - panic("short read on infinite random stream!?") + // readerBytes is how many bytes we expect from each source + readerBytes := 32 + + // try to read readerBytes bytes from all readers and write them in a buffer + var b bytes.Buffer + var nerr int + buff := make([]byte, readerBytes) + for _, reader := range r.Readers { + n, err := io.ReadFull(reader, buff) + if err != nil { + nerr++ + } + b.Write(buff[:n]) } - for i := 0; i < l; i++ { - dst[i] = src[i] ^ buf[i] + // we are ok with few sources being insecure (i.e., providing less than + // readerBytes bytes), but not all of them + if nerr == len(r.Readers) { + panic("all readers failed") } + + // create the XOF output, with hash of collected data as seed + h := sha256.New() + h.Write(b.Bytes()) + seed := h.Sum(nil) + blake2 := blake2xb.New(seed) + blake2.XORKeyStream(dst, src) } -// New returns a new cipher.Stream that gets random data from Go's crypto/rand package. +// New returns a new cipher.Stream that gets random data from the given +// readers. If no reader was provided, Go's crypto/rand package is used. +// Otherwise, for each source, 32 bytes are read. They are concatenated and +// then hashed, and the resulting hash is used as a seed to a PRNG. // The resulting cipher.Stream can be used in multiple threads. -func New() cipher.Stream { - return &randstream{} +func New(readers ...io.Reader) cipher.Stream { + if len(readers) == 0 { + readers = []io.Reader{rand.Reader} + } + return &randstream{readers} } diff --git a/util/random/rand_test.go b/util/random/rand_test.go new file mode 100644 index 000000000..42b50ac03 --- /dev/null +++ b/util/random/rand_test.go @@ -0,0 +1,86 @@ +package random + +import ( + "bytes" + "crypto/rand" + "strings" + "testing" +) + +const size = 32 + +func TestMixedEntropy(t *testing.T) { + r := strings.NewReader("some io.Reader stream to be used for testing") + cipher := New(r, rand.Reader) + + src := make([]byte, size) + copy(src, []byte("source buffer")) + dst := make([]byte, size+1) + dst[len(dst)-1] = 0xff + + cipher.XORKeyStream(dst[:len(dst)-1], src) + if len(src) > 0 && bytes.Equal(src, dst[0:len(src)]) { + t.Fatal("src and dst should not be equal") + } + if dst[len(dst)-1] != 0xff { + t.Fatal("last byte of dst chagned") + } +} + +func TestEmptyReader(t *testing.T) { + //expecting a panic + defer func() { + if r := recover(); r == nil { + t.Fatal("code did not panicked but should have") + } + }() + + r := strings.NewReader("too small io.Reader") + cipher := New(r) + src := make([]byte, size) + copy(src, []byte("hello")) + dst := make([]byte, size) + cipher.XORKeyStream(dst, src) +} + +func TestCryptoOnly(t *testing.T) { + cipher := New() + src := make([]byte, size) + copy(src, []byte("hello")) + dst1 := make([]byte, size) + cipher.XORKeyStream(dst1, src) + dst2 := make([]byte, size) + cipher.XORKeyStream(dst2, src) + if bytes.Equal(dst1, dst2) { + t.Fatal("dst1 and dst2 should not be equal") + } +} + +func TestUserOnly(t *testing.T) { + seed := "some io.Reader stream to be used for testing" + cipher1 := New(strings.NewReader(seed)) + src := make([]byte, size) + copy(src, []byte("hello")) + dst1 := make([]byte, size) + cipher1.XORKeyStream(dst1, src) + cipher2 := New(strings.NewReader(seed)) + dst2 := make([]byte, size) + cipher2.XORKeyStream(dst2, src) + if !bytes.Equal(dst1, dst2) { + t.Fatal("dst1/dst2 should be equal") + } +} + +func TestIncorrectSize(t *testing.T) { + //expecting a panic + defer func() { + if r := recover(); r == nil { + t.Fatal("code did not panicked but should have") + } + }() + cipher := New(rand.Reader) + src := make([]byte, size) + copy(src, []byte("hello")) + dst := make([]byte, size+1) + cipher.XORKeyStream(dst, src) +}