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
|
package kate
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"golang.org/x/crypto/poly1305" //nolint:staticcheck
"golang.org/x/crypto/salsa20"
)
type encryption struct {
Key [naclKeyLen]byte
}
var ErrInvalidToken = errors.New("invalid token")
func encryptionFromHexKey(key string) (encryption, error) {
e := encryption{}
l := hex.EncodedLen(naclKeyLen)
keybytes := []byte(key)
if l != len(keybytes) {
return e, fmt.Errorf("expected %d length encryption key, got %d", l, len(keybytes))
}
if _, err := hex.Decode(e.Key[:], keybytes); err != nil {
return e, fmt.Errorf("encryptionFromHexKey: %w", err)
}
return e, nil
}
func (e encryption) Encrypt(data []byte) string {
buf := make([]byte, naclNonceLen+len(data)+naclMacLen)
nonce := [naclNonceLen]byte{}
_, _ = rand.Read(nonce[:])
seal(buf[naclNonceLen:], e.Key, nonce, data)
copy(buf[:naclNonceLen], nonce[:])
return hex.EncodeToString(buf)
}
func (e encryption) Decrypt(token string) ([]byte, bool) {
tokenBytes := []byte(token)
ciphertext := make([]byte, hex.DecodedLen(len(tokenBytes)))
if _, err := hex.Decode(ciphertext, tokenBytes); err != nil {
return nil, false
}
nonce := ciphertext[:naclNonceLen]
noncebuf := [naclNonceLen]byte{}
copy(noncebuf[:], nonce)
ciphertext = ciphertext[naclNonceLen:]
cleartext := make([]byte, len(ciphertext)-naclMacLen)
if _, ok := open(cleartext, e.Key, noncebuf, ciphertext); !ok {
return nil, false
}
return cleartext, true
}
const (
naclKeyLen = 32
naclNonceLen = 24
naclMacLen = 16
)
//
// Replicating the libsodium secretbox API below.
//
// This was a last resort. I really didn't want to introduce CGO,
// and the only pure-go implementation I could find (libgodium)
// is unmaintained and riddled with bugs.
//
// With solid go implementations of the core primitives already in
// golang.org/x/crypto, it made the most sense to just wrap those.
//
func seal(dst []byte, key [naclKeyLen]byte, nonce [naclNonceLen]byte, plaintext []byte) []byte {
if len(dst) < len(plaintext)+naclMacLen {
dst = make([]byte, len(plaintext)+naclMacLen)
}
keystream := make([]byte, naclKeyLen+len(plaintext))
salsa20.XORKeyStream(keystream, keystream, nonce[:], &key)
var poly1305Key [naclKeyLen]byte
copy(poly1305Key[:], keystream[:naclKeyLen])
ciphertext := make([]byte, len(plaintext))
for i := range plaintext {
ciphertext[i] = plaintext[i] ^ keystream[naclKeyLen+i]
}
var mac [naclMacLen]byte
poly1305.Sum(&mac, ciphertext, &poly1305Key)
copy(dst[:naclMacLen], mac[:])
copy(dst[naclMacLen:], ciphertext)
return dst
}
func open(dst []byte, key [naclKeyLen]byte, nonce [naclNonceLen]byte, box []byte) ([]byte, bool) {
if len(box) < naclMacLen {
return nil, false
}
if len(dst) < len(box)-naclMacLen {
dst = make([]byte, len(box)-naclMacLen)
}
var receivedMAC [naclMacLen]byte
copy(receivedMAC[:], box[:naclMacLen])
ciphertext := box[naclMacLen:]
keystream := make([]byte, naclKeyLen+len(ciphertext))
salsa20.XORKeyStream(keystream, keystream, nonce[:], &key)
var poly1305Key [naclKeyLen]byte
copy(poly1305Key[:], keystream[:naclKeyLen])
var expectedMAC [naclMacLen]byte
poly1305.Sum(&expectedMAC, ciphertext, &poly1305Key)
if !poly1305.Verify(&receivedMAC, ciphertext, &poly1305Key) {
return nil, false
}
for i := range ciphertext {
dst[i] = ciphertext[i] ^ keystream[naclKeyLen+i]
}
return dst, true
}
|