-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathchacha20poly1305.go
164 lines (133 loc) · 4.29 KB
/
chacha20poly1305.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
// Copyright (c) 2017 Andreas Auernhammer. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
package chacha20poly1305
import (
"crypto/cipher"
"errors"
"crypto/subtle"
"encoding/binary"
"github.com/aead/chacha20/chacha"
"github.com/aead/poly1305"
)
// KeySize is the size of the key used by this AEAD, in bytes.
const KeySize = 32
var (
errBadKeySize = errors.New("chacha20poly1305: bad key length")
errAuthFailed = errors.New("chacha20poly1305: message authentication failed")
)
type c20p1305 struct {
key [32]byte
noncesize int
}
func newCipher(key []byte, noncesize int) (cipher.AEAD, error) {
if len(key) != KeySize {
return nil, errBadKeySize
}
c := &c20p1305{
noncesize: noncesize,
}
copy(c.key[:], key)
return c, nil
}
// NewCipher returns a cipher.AEAD implementing the
// ChaCha20Poly1305 construction specified in RFC 7539 with a
// 128 bit auth. tag.
func NewCipher(key []byte) (cipher.AEAD, error) {
return newCipher(key, chacha.NonceSize)
}
// NewIETFCipher returns a cipher.AEAD implementing the
// ChaCha20Poly1305 construction specified in RFC 7539 with a
// 128 bit auth. tag.
func NewIETFCipher(key []byte) (cipher.AEAD, error) {
return newCipher(key, chacha.INonceSize)
}
// NewXCipher returns a cipher.AEAD implementing the
// XChaCha20Poly1305 construction specified in RFC 7539 with a
// 128 bit auth. tag.
func NewXCipher(key []byte) (cipher.AEAD, error) {
return newCipher(key, chacha.XNonceSize)
}
func (c *c20p1305) Overhead() int { return poly1305.TagSize }
func (c *c20p1305) NonceSize() int { return c.noncesize }
func (c *c20p1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
if len(nonce) != c.NonceSize() {
panic("chacha20poly1305: bad nonce length passed to Seal")
}
if c.NonceSize() == chacha.INonceSize && uint64(len(plaintext)) > (1<<38)-64 {
panic("chacha20poly1305: plaintext too large")
}
var polyKey [32]byte
cipher, _ := chacha.NewCipher(nonce, c.key[:], 20)
cipher.XORKeyStream(polyKey[:], polyKey[:])
cipher.SetCounter(1)
n := len(plaintext)
ret, out := sliceForAppend(dst, n+c.Overhead())
cipher.XORKeyStream(out, plaintext)
var tag, pad [16]byte
hash := poly1305.New(polyKey)
hash.Write(additionalData)
if padAdd := len(additionalData) % 16; padAdd > 0 {
hash.Write(pad[:16-padAdd])
}
hash.Write(out[:n])
if padCt := n % 16; padCt > 0 {
hash.Write(pad[:16-padCt])
}
binary.LittleEndian.PutUint64(pad[:], uint64(len(additionalData)))
binary.LittleEndian.PutUint64(pad[8:], uint64(n))
hash.Write(pad[:])
hash.Sum(tag[:0])
copy(out[n:], tag[:])
return ret
}
func (c *c20p1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
if len(nonce) != c.NonceSize() {
panic("chacha20poly1305: bad nonce length passed to Open")
}
if len(ciphertext) < c.Overhead() {
return nil, errAuthFailed
}
if c.NonceSize() == chacha.INonceSize && uint64(len(ciphertext)) > (1<<38)-48 {
panic("chacha20poly1305: ciphertext too large")
}
var polyKey [32]byte
cipher, _ := chacha.NewCipher(nonce, c.key[:], 20)
cipher.XORKeyStream(polyKey[:], polyKey[:])
cipher.SetCounter(1)
n := len(ciphertext) - c.Overhead()
var tag, pad [16]byte
hash := poly1305.New(polyKey)
hash.Write(additionalData)
if padAdd := len(additionalData) % 16; padAdd > 0 {
hash.Write(pad[:16-padAdd])
}
hash.Write(ciphertext[:n])
if padCt := n % 16; padCt > 0 {
hash.Write(pad[:16-padCt])
}
binary.LittleEndian.PutUint64(pad[:], uint64(len(additionalData)))
binary.LittleEndian.PutUint64(pad[8:], uint64(n))
hash.Write(pad[:])
hash.Sum(tag[:0])
if subtle.ConstantTimeCompare(tag[:], ciphertext[n:]) != 1 {
return nil, errAuthFailed
}
ret, plaintext := sliceForAppend(dst, n)
cipher.XORKeyStream(plaintext, ciphertext[:n])
return ret, nil
}
// sliceForAppend takes a slice and a requested number of bytes. It returns a
// slice with the contents of the given slice followed by that many bytes and a
// second slice that aliases into it and contains only the extra bytes. If the
// original slice has sufficient capacity then no allocation is performed.
func sliceForAppend(in []byte, n int) (head, tail []byte) {
if total := len(in) + n; cap(in) >= total {
head = in[:total]
} else {
head = make([]byte, total)
copy(head, in)
}
tail = head[len(in):]
return
}