hs1siv.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // hs1siv.go - HS1-SIV
  2. //
  3. // To the extent possible under law, Yawning Angel has waived all copyright
  4. // and related or neighboring rights to the software, using the Creative
  5. // Commons "CC0" public domain dedication. See LICENSE or
  6. // <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
  7. // Package hs1siv implements the HS1-SIV Authenticated Cipher.
  8. //
  9. // While the specification defines multiple parameter sets, this implementation
  10. // deliberately only supprorts the most conservative "hs1-siv-hi".
  11. //
  12. // This implementation is derived from the reference implementation by Ted
  13. // Krovetz.
  14. package hs1siv
  15. import (
  16. "crypto/subtle"
  17. "encoding/binary"
  18. "errors"
  19. )
  20. const (
  21. // KeySize is the size of a key in bytes.
  22. KeySize = 32
  23. // NonceSize is the size of a nonce in bytes.
  24. NonceSize = 12
  25. // TagSize is the size of an authentication tag in bytes.
  26. TagSize = 32
  27. stateSize = chachaKeySize + hashStateSize
  28. )
  29. var (
  30. // ErrInvalidKeySize is the error thrown via a panic when a key is an
  31. // invalid size.
  32. ErrInvalidKeySize = errors.New("hs1siv: invalid key size")
  33. // ErrInvalidNonceSize is the error thrown via a panic when a nonce is
  34. // an invalid size.
  35. ErrInvalidNonceSize = errors.New("hs1siv: invalid nonce size")
  36. // ErrOpen is the error returned when the message authentication fails
  37. // during an Open call.
  38. ErrOpen = errors.New("hs1siv: message authentication failed")
  39. settings = [chachaNonceSize]byte{
  40. 0, 0, hs1SIVLen, 0, chachaRounds, hs1HashRounds, hs1NHLen,
  41. 0, 0, 0, 0,
  42. }
  43. zero [hs1SIVLen]byte
  44. )
  45. // AEAD is a HS1-SIV instance, implementing crypto/cipher.AEAD.
  46. type AEAD struct {
  47. key []byte
  48. }
  49. // NonceSize returns the size of the nonce that must be passed to Seal and
  50. // Open.
  51. func (ae *AEAD) NonceSize() int {
  52. return NonceSize
  53. }
  54. // Overhead returns the maximum difference between the lengths of a plaintext
  55. // and its ciphertext.
  56. func (ae *AEAD) Overhead() int {
  57. return TagSize
  58. }
  59. // Seal encrypts and authenticates plaintext, authenticates the
  60. // additional data and appends the result to dst, returning the updated
  61. // slice. The nonce must be NonceSize() bytes long and should be unique for
  62. // all time, for a given key.
  63. //
  64. // The plaintext and dst must overlap exactly or not at all. To reuse
  65. // plaintext's storage for the encrypted output, use plaintext[:0] as dst.
  66. func (ae *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
  67. if len(nonce) != NonceSize {
  68. panic(ErrInvalidNonceSize)
  69. }
  70. var ctx aeadCtx
  71. ctx.setup(ae.key)
  72. ret, out := sliceForAppend(dst, len(plaintext)+TagSize)
  73. ctx.encrypt(plaintext, additionalData, nonce, out)
  74. ctx.reset()
  75. return ret
  76. }
  77. // Open decrypts and authenticates ciphertext, authenticates the
  78. // additional data and, if successful, appends the resulting plaintext
  79. // to dst, returning the updated slice. The nonce must be NonceSize()
  80. // bytes long and both it and the additional data must match the
  81. // value passed to Seal.
  82. //
  83. // The ciphertext and dst must overlap exactly or not at all. To reuse
  84. // ciphertext's storage for the decrypted output, use ciphertext[:0] as dst.
  85. //
  86. // Even if the function fails, the contents of dst, up to its capacity,
  87. // may be overwritten.
  88. func (ae *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
  89. var err error
  90. var ok bool
  91. if len(nonce) != NonceSize {
  92. panic(ErrInvalidNonceSize)
  93. }
  94. var ctx aeadCtx
  95. ctx.setup(ae.key)
  96. ret, out := sliceForAppend(dst, len(ciphertext)-TagSize)
  97. ok = ctx.decrypt(ciphertext, additionalData, nonce, out)
  98. ctx.reset()
  99. if !ok {
  100. // On decryption failures, purge the invalid plaintext.
  101. if len(out) > 0 {
  102. burnBytes(out)
  103. ret = nil
  104. }
  105. err = ErrOpen
  106. }
  107. return ret, err
  108. }
  109. // Reset securely purges stored sensitive data from the AEAD instance.
  110. func (ae *AEAD) Reset() {
  111. burnBytes(ae.key)
  112. }
  113. // New returns a new keyed HS1-SIV instance.
  114. func New(key []byte) *AEAD {
  115. if len(key) != KeySize {
  116. panic(ErrInvalidKeySize)
  117. }
  118. return &AEAD{key: append([]byte{}, key...)}
  119. }
  120. type aeadCtx struct {
  121. chachaKey [chachaKeySize]byte
  122. hashCtx hs1Ctx
  123. sivAccum [hs1HashRounds]uint64
  124. sivLenBuf [16]byte
  125. }
  126. func (ctx *aeadCtx) reset() {
  127. burnBytes(ctx.chachaKey[:])
  128. ctx.hashCtx.reset()
  129. }
  130. // XOR first n bytes of src into dst, then copy the next 32-n bytes.
  131. func xorCopyChaChaKey(dst, src []byte) {
  132. const n = 24 // For 6 hash rounds.
  133. for i, v := range src[:n] {
  134. dst[i] ^= v
  135. }
  136. copy(dst[n:], src[n:])
  137. }
  138. func (ctx *aeadCtx) setup(userKey []byte) {
  139. // The paper allows a variable length key of up to 256 bits, the reference
  140. // implementation hard codes a 128 bit key.
  141. //
  142. // This implementation only supports a 256 bit key.
  143. var chachaNonce [chachaNonceSize]byte
  144. copy(chachaNonce[:], settings[:])
  145. chachaNonce[0] = byte(len(userKey))
  146. var buf [stateSize]byte
  147. chacha20(userKey, chachaNonce[:], buf[:], buf[:], 0)
  148. off := chachaKeySize
  149. copy(ctx.chachaKey[:], buf[:off])
  150. for i := range ctx.hashCtx.nhKey {
  151. ctx.hashCtx.nhKey[i] = binary.LittleEndian.Uint32(buf[off:])
  152. off += 4
  153. }
  154. for i := range ctx.hashCtx.polyKey {
  155. ctx.hashCtx.polyKey[i] = binary.LittleEndian.Uint64(buf[off:]) & m60
  156. off += 8
  157. }
  158. for i := range ctx.hashCtx.asuKey {
  159. ctx.hashCtx.asuKey[i] = binary.LittleEndian.Uint64(buf[off:])
  160. off += 8
  161. }
  162. burnBytes(buf[:])
  163. }
  164. func (ctx *aeadCtx) sivSetup(aBytes, mBytes int) {
  165. // Init: set up lengths, accumulator.
  166. binary.LittleEndian.PutUint64(ctx.sivLenBuf[0:8], uint64(aBytes))
  167. binary.LittleEndian.PutUint64(ctx.sivLenBuf[8:16], uint64(mBytes))
  168. for i := range ctx.sivAccum {
  169. ctx.sivAccum[i] = 1
  170. }
  171. }
  172. func (ctx *aeadCtx) sivHashAD(a []byte) {
  173. aBytes := len(a)
  174. // Hash associated data.
  175. nhMultiple := aBytes & ^(hs1NHLen - 1)
  176. hashStep(&ctx.hashCtx, a[:nhMultiple], &ctx.sivAccum)
  177. if nhMultiple < aBytes {
  178. var buf [hs1NHLen]byte
  179. copy(buf[:], a[nhMultiple:])
  180. hashStep(&ctx.hashCtx, buf[:], &ctx.sivAccum)
  181. }
  182. }
  183. func (ctx *aeadCtx) sivGenerate(m, n, siv []byte) {
  184. mBytes := len(m)
  185. // Hash message data.
  186. var chachaKey [chachaKeySize]byte
  187. nhMultiple := mBytes & ^(hs1NHLen - 1)
  188. hashStep(&ctx.hashCtx, m[:nhMultiple], &ctx.sivAccum)
  189. mBytes = mBytes - nhMultiple
  190. mBytesWithPadding := (mBytes + 15) & ^15
  191. if mBytesWithPadding == hs1NHLen {
  192. var buf [hs1NHLen]byte
  193. copy(buf[:], m[nhMultiple:])
  194. hashStep(&ctx.hashCtx, buf[:], &ctx.sivAccum)
  195. hashFinalizeRef(&ctx.hashCtx, ctx.sivLenBuf[:], &ctx.sivAccum, chachaKey[:])
  196. } else {
  197. var buf [hs1NHLen]byte
  198. copy(buf[:], m[nhMultiple:])
  199. copy(buf[mBytesWithPadding:], ctx.sivLenBuf[:])
  200. hashFinalizeRef(&ctx.hashCtx, buf[:mBytesWithPadding+16], &ctx.sivAccum, chachaKey[:])
  201. }
  202. // Derive the SIV.
  203. xorCopyChaChaKey(chachaKey[:], ctx.chachaKey[:])
  204. chacha20(chachaKey[:], n, zero[:], siv, 0)
  205. burnBytes(chachaKey[:])
  206. }
  207. func (ctx *aeadCtx) encrypt(m, a, n, c []byte) {
  208. mBytes := len(m)
  209. var accum [hs1HashRounds]uint64
  210. for i := range accum {
  211. accum[i] = 1
  212. }
  213. var siv [hs1SIVLen]byte
  214. ctx.sivSetup(len(a), len(m))
  215. ctx.sivHashAD(a)
  216. ctx.sivGenerate(m, n, siv[:])
  217. var chachaKey [chachaKeySize]byte
  218. hashFinalizeRef(&ctx.hashCtx, siv[:], &accum, chachaKey[:])
  219. xorCopyChaChaKey(chachaKey[:], ctx.chachaKey[:])
  220. chacha20(chachaKey[:], n, m, c, 1)
  221. copy(c[mBytes:], siv[:])
  222. burnBytes(chachaKey[:])
  223. }
  224. func (ctx *aeadCtx) decrypt(c, a, n, m []byte) bool {
  225. cBytes := len(c)
  226. if cBytes < hs1SIVLen {
  227. return false
  228. }
  229. mBytes := cBytes - hs1SIVLen
  230. var accum [hs1HashRounds]uint64
  231. for i := range accum {
  232. accum[i] = 1
  233. }
  234. var siv, maybeSIV [hs1SIVLen]byte
  235. var nonce [NonceSize]byte
  236. copy(siv[:], c[mBytes:])
  237. copy(nonce[:], n) // Work with a copy, `m` and `n` may alias.
  238. var chachaKey [chachaKeySize]byte
  239. hashFinalizeRef(&ctx.hashCtx, siv[:], &accum, chachaKey[:])
  240. xorCopyChaChaKey(chachaKey[:], ctx.chachaKey[:])
  241. ctx.sivSetup(len(a), len(m))
  242. ctx.sivHashAD(a) // Hash AD before decrption, `m` and `a` may alias.
  243. chacha20(chachaKey[:], nonce[:], c[:mBytes], m, 1)
  244. ctx.sivGenerate(m, nonce[:], maybeSIV[:])
  245. return subtle.ConstantTimeCompare(siv[:], maybeSIV[:]) == 1
  246. }
  247. // Shamelessly stolen from the Go runtime library.
  248. func sliceForAppend(in []byte, n int) (head, tail []byte) {
  249. if total := len(in) + n; cap(in) >= total {
  250. head = in[:total]
  251. } else {
  252. head = make([]byte, total)
  253. copy(head, in)
  254. }
  255. tail = head[len(in):]
  256. return
  257. }