Browse Source

crypto/random: Use chacha20, add forward secrecy (#2562)

Ref #2099
pull/2576/head
Dev Ojha 6 years ago
committed by Alexander Simmerl
parent
commit
b1e7fac787
3 changed files with 60 additions and 25 deletions
  1. +1
    -0
      CHANGELOG_PENDING.md
  2. +36
    -25
      crypto/random.go
  3. +23
    -0
      crypto/random_test.go

+ 1
- 0
CHANGELOG_PENDING.md View File

@ -38,6 +38,7 @@ IMPROVEMENTS:
- [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) add additional metrics - [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 - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) add additional metrics
- [config] \#2232 added ValidateBasic method, which performs basic checks - [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: BUG FIXES:
- [autofile] \#2428 Group.RotateFile need call Flush() before rename (@goolAdapter) - [autofile] \#2428 Group.RotateFile need call Flush() before rename (@goolAdapter)


+ 36
- 25
crypto/random.go View File

@ -1,7 +1,6 @@
package crypto package crypto
import ( import (
"crypto/aes"
"crypto/cipher" "crypto/cipher"
crand "crypto/rand" crand "crypto/rand"
"crypto/sha256" "crypto/sha256"
@ -9,9 +8,17 @@ import (
"io" "io"
"sync" "sync"
"golang.org/x/crypto/chacha20poly1305"
. "github.com/tendermint/tendermint/libs/common" . "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 var gRandInfo *randInfo
func init() { func init() {
@ -61,11 +68,10 @@ func CReader() io.Reader {
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
type randInfo struct { 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. // 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(seedBytes)
h.Write(ri.seedBytes[:]) h.Write(ri.seedBytes[:])
hashBytes := h.Sum(nil) 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 { 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 // 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
} }

+ 23
- 0
crypto/random_test.go View File

@ -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)
}

Loading…
Cancel
Save