-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathwriter.go
173 lines (154 loc) · 4.36 KB
/
writer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package chacha20poly1305
import (
"crypto/subtle"
"encoding/binary"
"io"
"github.com/aead/chacha20/chacha"
"github.com/aead/poly1305"
)
const tagsize = poly1305.TagSize
// EncryptWriter wraps an io.Writer and returns an io.WriteCloser which
// encrypts and authenticates all input passed into it with the given key and
// nonce. The Close function of the returned io.WriteCloser must be called
// to finish the encryption successfully.
//
// If the Write or Close function of the io.WriteCloser returns a non-nil error
// the hole encryption process cannot succeed and must be restarted.
// The length of the nonce determines which cipher is used:
// - 8 byte: ChaCha20Poly1305 with 64 bit nonces
// - 12 byte: ChaCha20Poly1305 with 96 bit nonces (used in TLS)
// - 24 byte: XChaCha20Poly1305 with 192 bit nonces
func EncryptWriter(w io.Writer, key, nonce []byte) (io.WriteCloser, error) {
enc, err := newAeadCipher(w, key, nonce)
if err != nil {
return nil, err
}
return &encryptedWriter{aeadCipher: *enc}, nil
}
// DecryptWriter wraps an io.Writer and returns an io.WriteCloser which
// decrypts and checks authenticity of all input passed into it with the given key
// and nonce. The Close function of the returned io.WriteCloser must be called
// to finish the decryption successfully. If the Close function returns a non-nil
// error the decryption failed - for example because of an incorrect authentication tag.
// So the returned error of Close MUST be checked!
//
// If the Write or Close function of the io.WriteCloser returns a non-nil error
// the hole decryption process cannot succeed and must be restarted.
// The length of the nonce determines which cipher is used:
// - 8 byte: ChaCha20Poly1305 with 64 bit nonces
// - 12 byte: ChaCha20Poly1305 with 96 bit nonces (used in TLS)
// - 24 byte: XChaCha20Poly1305 with 192 bit nonces
func DecryptWriter(w io.Writer, key, nonce []byte) (io.WriteCloser, error) {
dec, err := newAeadCipher(w, key, nonce)
if err != nil {
return nil, err
}
return &decryptedWriter{aeadCipher: *dec}, nil
}
type aeadCipher struct {
cipher *chacha.Cipher
hash *poly1305.Hash
dst io.Writer
byteCtr uint64
}
func newAeadCipher(w io.Writer, key, nonce []byte) (*aeadCipher, error) {
c, err := chacha.NewCipher(nonce, key, 20)
if err != nil {
return nil, err
}
var polyKey [32]byte
c.XORKeyStream(polyKey[:], polyKey[:])
c.SetCounter(1)
return &aeadCipher{
cipher: c,
hash: poly1305.New(polyKey),
dst: w,
}, nil
}
func (c *aeadCipher) encrypt(p []byte) (n int, err error) {
c.cipher.XORKeyStream(p, p)
c.hash.Write(p)
n, err = c.dst.Write(p)
return
}
func (c *aeadCipher) decrypt(p []byte) (n int, err error) {
c.hash.Write(p)
c.cipher.XORKeyStream(p, p)
n, err = c.dst.Write(p)
return
}
func (c *aeadCipher) authenticate(tag *[tagsize]byte) {
var pad [tagsize]byte
if padCt := c.byteCtr % tagsize; padCt > 0 {
c.hash.Write(pad[:tagsize-padCt])
}
binary.LittleEndian.PutUint64(pad[8:], c.byteCtr)
c.hash.Write(pad[:])
c.hash.Sum(tag[:0])
}
type encryptedWriter struct {
aeadCipher
}
func (w *encryptedWriter) Write(p []byte) (n int, err error) {
w.byteCtr += uint64(len(p))
return w.encrypt(p)
}
func (w *encryptedWriter) Close() error {
var tag [tagsize]byte
w.authenticate(&tag)
_, err := w.dst.Write(tag[:])
return err
}
type decryptedWriter struct {
aeadCipher
buf [tagsize]byte
off int
}
func (w *decryptedWriter) Write(p []byte) (n int, err error) {
n = len(p)
w.byteCtr += uint64(len(p))
k := w.off + n
if k < tagsize {
w.off += copy(w.buf[w.off:], p)
return
}
if w.off > 0 && k < 2*tagsize {
var tmp [2 * tagsize]byte
o := copy(tmp[:], w.buf[:w.off])
o += copy(tmp[o:], p)
i := o - tagsize
if n, err := w.decrypt(tmp[:i]); err != nil {
return n, err
}
w.off = copy(w.buf[:], tmp[i:o])
return
}
if w.off > 0 {
r := tagsize - w.off
copy(w.buf[w.off:], p[:r])
if n, err := w.decrypt(w.buf[:]); err != nil {
return n, err
}
p = p[r:]
w.off = 0
}
if nn := len(p) - tagsize; nn > 0 {
if n, err := w.decrypt(p[:nn]); err != nil {
return n, err
}
p = p[nn:]
}
if len(p) > 0 {
w.off = copy(w.buf[:], p)
}
return
}
func (w *decryptedWriter) Close() error {
w.byteCtr -= tagsize // we only count the ciphertext, not the tag
var tag [tagsize]byte
w.authenticate(&tag)
if subtle.ConstantTimeCompare(tag[:], w.buf[:]) != 1 {
return errAuthFailed
}
return nil
}