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.

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