1 package aescbc
2
3 import (
4 "crypto/cipher"
5 "crypto/hmac"
6 "crypto/sha256"
7 "crypto/sha512"
8 "crypto/subtle"
9 "encoding/binary"
10 "fmt"
11 "hash"
12
13 "github.com/pkg/errors"
14 )
15
16 const (
17 NonceSize = 16
18 )
19
20 func pad(buf []byte, n int) []byte {
21 rem := n - len(buf)%n
22 if rem == 0 {
23 return buf
24 }
25
26 newbuf := make([]byte, len(buf)+rem)
27 copy(newbuf, buf)
28
29 for i := len(buf); i < len(newbuf); i++ {
30 newbuf[i] = byte(rem)
31 }
32 return newbuf
33 }
34
35 func unpad(buf []byte, n int) ([]byte, error) {
36 lbuf := len(buf)
37 rem := lbuf % n
38
39
40 if rem != 0 {
41 return nil, errors.Errorf("input buffer must be multiple of block size %d", n)
42 }
43
44
45
46 last := buf[lbuf-1]
47
48
49 expected := int(last)
50
51 if expected == 0 ||
52 expected > n ||
53 expected > lbuf {
54 return nil, fmt.Errorf(`invalid padding byte at the end of buffer`)
55 }
56
57
58
59
60
61 for i := 1; i < expected; i++ {
62 if buf[lbuf-i] != last {
63 return nil, errors.New(`invalid padding`)
64 }
65 }
66
67 return buf[:lbuf-expected], nil
68 }
69
70 type Hmac struct {
71 blockCipher cipher.Block
72 hash func() hash.Hash
73 keysize int
74 tagsize int
75 integrityKey []byte
76 }
77
78 type BlockCipherFunc func([]byte) (cipher.Block, error)
79
80 func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
81 keysize := len(key) / 2
82 ikey := key[:keysize]
83 ekey := key[keysize:]
84
85 bc, ciphererr := f(ekey)
86 if ciphererr != nil {
87 err = errors.Wrap(ciphererr, `failed to execute block cipher function`)
88 return
89 }
90
91 var hfunc func() hash.Hash
92 switch keysize {
93 case 16:
94 hfunc = sha256.New
95 case 24:
96 hfunc = sha512.New384
97 case 32:
98 hfunc = sha512.New
99 default:
100 return nil, errors.Errorf("unsupported key size %d", keysize)
101 }
102
103 return &Hmac{
104 blockCipher: bc,
105 hash: hfunc,
106 integrityKey: ikey,
107 keysize: keysize,
108 tagsize: keysize,
109
110
111
112
113 }, nil
114 }
115
116
117 func (c Hmac) NonceSize() int {
118 return NonceSize
119 }
120
121
122 func (c Hmac) Overhead() int {
123 return c.blockCipher.BlockSize() + c.tagsize
124 }
125
126 func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) {
127 buf := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
128 n := 0
129 n += copy(buf, aad)
130 n += copy(buf[n:], nonce)
131 n += copy(buf[n:], ciphertext)
132 binary.BigEndian.PutUint64(buf[n:], uint64(len(aad)*8))
133
134 h := hmac.New(c.hash, c.integrityKey)
135 if _, err := h.Write(buf); err != nil {
136 return nil, errors.Wrap(err, "failed to write ComputeAuthTag using Hmac")
137 }
138 s := h.Sum(nil)
139 return s[:c.tagsize], nil
140 }
141
142 func ensureSize(dst []byte, n int) []byte {
143
144
145
146 ret := dst
147 if diff := n - len(dst); diff > 0 {
148
149 ret = make([]byte, n)
150 copy(ret, dst)
151 }
152 return ret
153 }
154
155
156 func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte {
157 ctlen := len(plaintext)
158 ciphertext := make([]byte, ctlen+c.Overhead())[:ctlen]
159 copy(ciphertext, plaintext)
160 ciphertext = pad(ciphertext, c.blockCipher.BlockSize())
161
162 cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce)
163 cbc.CryptBlocks(ciphertext, ciphertext)
164
165 authtag, err := c.ComputeAuthTag(data, nonce, ciphertext)
166 if err != nil {
167
168
169 panic(fmt.Errorf("failed to seal on hmac: %v", err))
170 }
171
172 retlen := len(dst) + len(ciphertext) + len(authtag)
173
174 ret := ensureSize(dst, retlen)
175 out := ret[len(dst):]
176 n := copy(out, ciphertext)
177 copy(out[n:], authtag)
178
179 return ret
180 }
181
182
183 func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
184 if len(ciphertext) < c.keysize {
185 return nil, errors.New("invalid ciphertext (too short)")
186 }
187
188 tagOffset := len(ciphertext) - c.tagsize
189 if tagOffset%c.blockCipher.BlockSize() != 0 {
190 return nil, fmt.Errorf(
191 "invalid ciphertext (invalid length: %d %% %d != 0)",
192 tagOffset,
193 c.blockCipher.BlockSize(),
194 )
195 }
196 tag := ciphertext[tagOffset:]
197 ciphertext = ciphertext[:tagOffset]
198
199 expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset])
200 if err != nil {
201 return nil, errors.Wrap(err, `failed to compute auth tag`)
202 }
203
204 if subtle.ConstantTimeCompare(expectedTag, tag) != 1 {
205 return nil, errors.New("invalid ciphertext (tag mismatch)")
206 }
207
208 cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce)
209 buf := make([]byte, tagOffset)
210 cbc.CryptBlocks(buf, ciphertext)
211
212 plaintext, err := unpad(buf, c.blockCipher.BlockSize())
213 if err != nil {
214 return nil, errors.Wrap(err, `failed to generate plaintext from decrypted blocks`)
215 }
216 ret := ensureSize(dst, len(plaintext))
217 out := ret[len(dst):]
218 copy(out, plaintext)
219 return ret, nil
220 }
221
View as plain text