aescbc.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package aescbc
  2. import (
  3. "crypto/cipher"
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "crypto/sha512"
  7. "crypto/subtle"
  8. "encoding/binary"
  9. "fmt"
  10. "hash"
  11. "github.com/pkg/errors"
  12. )
  13. const (
  14. NonceSize = 16
  15. )
  16. func pad(buf []byte, n int) []byte {
  17. rem := n - len(buf)%n
  18. if rem == 0 {
  19. return buf
  20. }
  21. newbuf := make([]byte, len(buf)+rem)
  22. copy(newbuf, buf)
  23. for i := len(buf); i < len(newbuf); i++ {
  24. newbuf[i] = byte(rem)
  25. }
  26. return newbuf
  27. }
  28. func unpad(buf []byte, n int) ([]byte, error) {
  29. lbuf := len(buf)
  30. rem := lbuf % n
  31. // First, `buf` must be a multiple of `n`
  32. if rem != 0 {
  33. return nil, errors.Errorf("input buffer must be multiple of block size %d", n)
  34. }
  35. // Find the last byte, which is the encoded padding
  36. // i.e. 0x1 == 1 byte worth of padding
  37. last := buf[lbuf-1]
  38. // This is the number of padding bytes that we expect
  39. expected := int(last)
  40. if expected == 0 || /* we _have_ to have padding here. therefore, 0x0 is not an option */
  41. expected > n || /* we also must make sure that we don't go over the block size (n) */
  42. expected > lbuf /* finally, it can't be more than the buffer itself. unlikely, but could happen */ {
  43. return nil, fmt.Errorf(`invalid padding byte at the end of buffer`)
  44. }
  45. // start i = 1 because we have already established that expected == int(last) where
  46. // last = buf[lbuf-1].
  47. //
  48. // we also don't check against lbuf-i in range, because we have established expected <= lbuf
  49. for i := 1; i < expected; i++ {
  50. if buf[lbuf-i] != last {
  51. return nil, errors.New(`invalid padding`)
  52. }
  53. }
  54. return buf[:lbuf-expected], nil
  55. }
  56. type Hmac struct {
  57. blockCipher cipher.Block
  58. hash func() hash.Hash
  59. keysize int
  60. tagsize int
  61. integrityKey []byte
  62. }
  63. type BlockCipherFunc func([]byte) (cipher.Block, error)
  64. func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
  65. keysize := len(key) / 2
  66. ikey := key[:keysize]
  67. ekey := key[keysize:]
  68. bc, ciphererr := f(ekey)
  69. if ciphererr != nil {
  70. err = errors.Wrap(ciphererr, `failed to execute block cipher function`)
  71. return
  72. }
  73. var hfunc func() hash.Hash
  74. switch keysize {
  75. case 16:
  76. hfunc = sha256.New
  77. case 24:
  78. hfunc = sha512.New384
  79. case 32:
  80. hfunc = sha512.New
  81. default:
  82. return nil, errors.Errorf("unsupported key size %d", keysize)
  83. }
  84. return &Hmac{
  85. blockCipher: bc,
  86. hash: hfunc,
  87. integrityKey: ikey,
  88. keysize: keysize,
  89. tagsize: keysize, // NonceSize,
  90. // While investigating GH #207, I stumbled upon another problem where
  91. // the computed tags don't match on decrypt. After poking through the
  92. // code using a bunch of debug statements, I've finally found out that
  93. // tagsize = keysize makes the whole thing work.
  94. }, nil
  95. }
  96. // NonceSize fulfills the crypto.AEAD interface
  97. func (c Hmac) NonceSize() int {
  98. return NonceSize
  99. }
  100. // Overhead fulfills the crypto.AEAD interface
  101. func (c Hmac) Overhead() int {
  102. return c.blockCipher.BlockSize() + c.tagsize
  103. }
  104. func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) {
  105. buf := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
  106. n := 0
  107. n += copy(buf, aad)
  108. n += copy(buf[n:], nonce)
  109. n += copy(buf[n:], ciphertext)
  110. binary.BigEndian.PutUint64(buf[n:], uint64(len(aad)*8))
  111. h := hmac.New(c.hash, c.integrityKey)
  112. if _, err := h.Write(buf); err != nil {
  113. return nil, errors.Wrap(err, "failed to write ComputeAuthTag using Hmac")
  114. }
  115. s := h.Sum(nil)
  116. return s[:c.tagsize], nil
  117. }
  118. func ensureSize(dst []byte, n int) []byte {
  119. // if the dst buffer has enough length just copy the relevant parts to it.
  120. // Otherwise create a new slice that's big enough, and operate on that
  121. // Note: I think go-jose has a bug in that it checks for cap(), but not len().
  122. ret := dst
  123. if diff := n - len(dst); diff > 0 {
  124. // dst is not big enough
  125. ret = make([]byte, n)
  126. copy(ret, dst)
  127. }
  128. return ret
  129. }
  130. // Seal fulfills the crypto.AEAD interface
  131. func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte {
  132. ctlen := len(plaintext)
  133. ciphertext := make([]byte, ctlen+c.Overhead())[:ctlen]
  134. copy(ciphertext, plaintext)
  135. ciphertext = pad(ciphertext, c.blockCipher.BlockSize())
  136. cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce)
  137. cbc.CryptBlocks(ciphertext, ciphertext)
  138. authtag, err := c.ComputeAuthTag(data, nonce, ciphertext)
  139. if err != nil {
  140. // Hmac implements cipher.AEAD interface. Seal can't return error.
  141. // But currently it never reach here because of Hmac.ComputeAuthTag doesn't return error.
  142. panic(fmt.Errorf("failed to seal on hmac: %v", err))
  143. }
  144. retlen := len(dst) + len(ciphertext) + len(authtag)
  145. ret := ensureSize(dst, retlen)
  146. out := ret[len(dst):]
  147. n := copy(out, ciphertext)
  148. copy(out[n:], authtag)
  149. return ret
  150. }
  151. // Open fulfills the crypto.AEAD interface
  152. func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
  153. if len(ciphertext) < c.keysize {
  154. return nil, errors.New("invalid ciphertext (too short)")
  155. }
  156. tagOffset := len(ciphertext) - c.tagsize
  157. if tagOffset%c.blockCipher.BlockSize() != 0 {
  158. return nil, fmt.Errorf(
  159. "invalid ciphertext (invalid length: %d %% %d != 0)",
  160. tagOffset,
  161. c.blockCipher.BlockSize(),
  162. )
  163. }
  164. tag := ciphertext[tagOffset:]
  165. ciphertext = ciphertext[:tagOffset]
  166. expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset])
  167. if err != nil {
  168. return nil, errors.Wrap(err, `failed to compute auth tag`)
  169. }
  170. if subtle.ConstantTimeCompare(expectedTag, tag) != 1 {
  171. return nil, errors.New("invalid ciphertext (tag mismatch)")
  172. }
  173. cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce)
  174. buf := make([]byte, tagOffset)
  175. cbc.CryptBlocks(buf, ciphertext)
  176. plaintext, err := unpad(buf, c.blockCipher.BlockSize())
  177. if err != nil {
  178. return nil, errors.Wrap(err, `failed to generate plaintext from decrypted blocks`)
  179. }
  180. ret := ensureSize(dst, len(plaintext))
  181. out := ret[len(dst):]
  182. copy(out, plaintext)
  183. return ret, nil
  184. }