You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.1 KiB

  1. package crypto
  2. import (
  3. "crypto/cipher"
  4. crand "crypto/rand"
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "io"
  8. "sync"
  9. "golang.org/x/crypto/chacha20poly1305"
  10. . "github.com/tendermint/tendermint/libs/common"
  11. )
  12. // The randomness here is derived from xoring a chacha20 keystream with
  13. // output from crypto/rand's OS Entropy Reader. (Due to fears of the OS'
  14. // entropy being backdoored)
  15. //
  16. // For forward secrecy of produced randomness, the internal chacha key is hashed
  17. // and thereby rotated after each call.
  18. var gRandInfo *randInfo
  19. func init() {
  20. gRandInfo = &randInfo{}
  21. gRandInfo.MixEntropy(randBytes(32)) // Init
  22. }
  23. // Mix additional bytes of randomness, e.g. from hardware, user-input, etc.
  24. // It is OK to call it multiple times. It does not diminish security.
  25. func MixEntropy(seedBytes []byte) {
  26. gRandInfo.MixEntropy(seedBytes)
  27. }
  28. // This only uses the OS's randomness
  29. func randBytes(numBytes int) []byte {
  30. b := make([]byte, numBytes)
  31. _, err := crand.Read(b)
  32. if err != nil {
  33. PanicCrisis(err)
  34. }
  35. return b
  36. }
  37. // This uses the OS and the Seed(s).
  38. func CRandBytes(numBytes int) []byte {
  39. b := make([]byte, numBytes)
  40. _, err := gRandInfo.Read(b)
  41. if err != nil {
  42. PanicCrisis(err)
  43. }
  44. return b
  45. }
  46. // CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long.
  47. //
  48. // Note: CRandHex(24) gives 96 bits of randomness that
  49. // are usually strong enough for most purposes.
  50. func CRandHex(numDigits int) string {
  51. return hex.EncodeToString(CRandBytes(numDigits / 2))
  52. }
  53. // Returns a crand.Reader mixed with user-supplied entropy
  54. func CReader() io.Reader {
  55. return gRandInfo
  56. }
  57. //--------------------------------------------------------------------------------
  58. type randInfo struct {
  59. mtx sync.Mutex
  60. seedBytes [chacha20poly1305.KeySize]byte
  61. chacha cipher.AEAD
  62. reader io.Reader
  63. }
  64. // You can call this as many times as you'd like.
  65. // XXX TODO review
  66. func (ri *randInfo) MixEntropy(seedBytes []byte) {
  67. ri.mtx.Lock()
  68. defer ri.mtx.Unlock()
  69. // Make new ri.seedBytes using passed seedBytes and current ri.seedBytes:
  70. // ri.seedBytes = sha256( seedBytes || ri.seedBytes )
  71. h := sha256.New()
  72. h.Write(seedBytes)
  73. h.Write(ri.seedBytes[:])
  74. hashBytes := h.Sum(nil)
  75. copy(ri.seedBytes[:], hashBytes)
  76. chacha, err := chacha20poly1305.New(ri.seedBytes[:])
  77. if err != nil {
  78. panic("Initializing chacha20 failed")
  79. }
  80. ri.chacha = chacha
  81. // Create new reader
  82. ri.reader = &cipher.StreamReader{S: ri, R: crand.Reader}
  83. }
  84. func (ri *randInfo) XORKeyStream(dst, src []byte) {
  85. // nonce being 0 is safe due to never re-using a key.
  86. emptyNonce := make([]byte, 12)
  87. tmpDst := ri.chacha.Seal([]byte{}, emptyNonce, src, []byte{0})
  88. // this removes the poly1305 tag as well, since chacha is a stream cipher
  89. // and we truncate at input length.
  90. copy(dst, tmpDst[:len(src)])
  91. // hash seedBytes for forward secrecy, and initialize new chacha instance
  92. newSeed := sha256.Sum256(ri.seedBytes[:])
  93. chacha, err := chacha20poly1305.New(newSeed[:])
  94. if err != nil {
  95. panic("Initializing chacha20 failed")
  96. }
  97. ri.chacha = chacha
  98. }
  99. func (ri *randInfo) Read(b []byte) (n int, err error) {
  100. ri.mtx.Lock()
  101. n, err = ri.reader.Read(b)
  102. ri.mtx.Unlock()
  103. return
  104. }