Skip to content

Commit

Permalink
Merge pull request #391 from PizzaWhisperer/rand
Browse files Browse the repository at this point in the history
New randomStream struct to allow use of user-specified entropy source
  • Loading branch information
Jeff R. Allen authored Oct 10, 2019
2 parents fb89417 + 6755f0c commit 4820853
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 15 deletions.
54 changes: 39 additions & 15 deletions util/random/rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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}
}
86 changes: 86 additions & 0 deletions util/random/rand_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 4820853

Please sign in to comment.