From b1e7fac787923bb92a2e720b45edf5ca5971b84f Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 8 Oct 2018 06:15:56 -0700 Subject: [PATCH] crypto/random: Use chacha20, add forward secrecy (#2562) Ref #2099 --- CHANGELOG_PENDING.md | 1 + crypto/random.go | 61 +++++++++++++++++++++++++------------------ crypto/random_test.go | 23 ++++++++++++++++ 3 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 crypto/random_test.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6a3d6f00d..1a927a329 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -38,6 +38,7 @@ IMPROVEMENTS: - [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) add additional metrics - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) add additional metrics - [config] \#2232 added ValidateBasic method, which performs basic checks +- [crypto] \#2099 make crypto random use chacha, and have forward secrecy of generated randomness BUG FIXES: - [autofile] \#2428 Group.RotateFile need call Flush() before rename (@goolAdapter) diff --git a/crypto/random.go b/crypto/random.go index 5c5057d30..af3286427 100644 --- a/crypto/random.go +++ b/crypto/random.go @@ -1,7 +1,6 @@ package crypto import ( - "crypto/aes" "crypto/cipher" crand "crypto/rand" "crypto/sha256" @@ -9,9 +8,17 @@ import ( "io" "sync" + "golang.org/x/crypto/chacha20poly1305" + . "github.com/tendermint/tendermint/libs/common" ) +// The randomness here is derived from xoring a chacha20 keystream with +// output from crypto/rand's OS Entropy Reader. (Due to fears of the OS' +// entropy being backdoored) +// +// For forward secrecy of produced randomness, the internal chacha key is hashed +// and thereby rotated after each call. var gRandInfo *randInfo func init() { @@ -61,11 +68,10 @@ func CReader() io.Reader { //-------------------------------------------------------------------------------- type randInfo struct { - mtx sync.Mutex - seedBytes [32]byte - cipherAES256 cipher.Block - streamAES256 cipher.Stream - reader io.Reader + mtx sync.Mutex + seedBytes [chacha20poly1305.KeySize]byte + chacha cipher.AEAD + reader io.Reader } // You can call this as many times as you'd like. @@ -79,30 +85,35 @@ func (ri *randInfo) MixEntropy(seedBytes []byte) { h.Write(seedBytes) h.Write(ri.seedBytes[:]) hashBytes := h.Sum(nil) - hashBytes32 := [32]byte{} - copy(hashBytes32[:], hashBytes) - ri.seedBytes = xorBytes32(ri.seedBytes, hashBytes32) - // Create new cipher.Block - var err error - ri.cipherAES256, err = aes.NewCipher(ri.seedBytes[:]) + copy(ri.seedBytes[:], hashBytes) + chacha, err := chacha20poly1305.New(ri.seedBytes[:]) if err != nil { - PanicSanity("Error creating AES256 cipher: " + err.Error()) + panic("Initializing chacha20 failed") } - // Create new stream - ri.streamAES256 = cipher.NewCTR(ri.cipherAES256, randBytes(aes.BlockSize)) + ri.chacha = chacha // Create new reader - ri.reader = &cipher.StreamReader{S: ri.streamAES256, R: crand.Reader} + ri.reader = &cipher.StreamReader{S: ri, R: crand.Reader} } -func (ri *randInfo) Read(b []byte) (n int, err error) { - ri.mtx.Lock() - defer ri.mtx.Unlock() - return ri.reader.Read(b) +func (ri *randInfo) XORKeyStream(dst, src []byte) { + // nonce being 0 is safe due to never re-using a key. + emptyNonce := make([]byte, 12) + tmpDst := ri.chacha.Seal([]byte{}, emptyNonce, src, []byte{0}) + // this removes the poly1305 tag as well, since chacha is a stream cipher + // and we truncate at input length. + copy(dst, tmpDst[:len(src)]) + // hash seedBytes for forward secrecy, and initialize new chacha instance + newSeed := sha256.Sum256(ri.seedBytes[:]) + chacha, err := chacha20poly1305.New(newSeed[:]) + if err != nil { + panic("Initializing chacha20 failed") + } + ri.chacha = chacha } -func xorBytes32(bytesA [32]byte, bytesB [32]byte) (res [32]byte) { - for i, b := range bytesA { - res[i] = b ^ bytesB[i] - } - return res +func (ri *randInfo) Read(b []byte) (n int, err error) { + ri.mtx.Lock() + n, err = ri.reader.Read(b) + ri.mtx.Unlock() + return } diff --git a/crypto/random_test.go b/crypto/random_test.go new file mode 100644 index 000000000..34f7372fe --- /dev/null +++ b/crypto/random_test.go @@ -0,0 +1,23 @@ +package crypto_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" +) + +// the purpose of this test is primarily to ensure that the randomness +// generation won't error. +func TestRandomConsistency(t *testing.T) { + x1 := crypto.CRandBytes(256) + x2 := crypto.CRandBytes(256) + x3 := crypto.CRandBytes(256) + x4 := crypto.CRandBytes(256) + x5 := crypto.CRandBytes(256) + require.NotEqual(t, x1, x2) + require.NotEqual(t, x3, x4) + require.NotEqual(t, x4, x5) + require.NotEqual(t, x1, x5) +}