-
Notifications
You must be signed in to change notification settings - Fork 2
/
xsecretbox.go
115 lines (98 loc) · 2.6 KB
/
xsecretbox.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
package xsecretbox
import (
"crypto/subtle"
"errors"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/poly1305"
)
const (
// KeySize is what the name suggests
KeySize = 32
// NonceSize is what the name suggests
NonceSize = 24
// TagSize is what the name suggests
TagSize = 16
)
// Seal does what the name suggests
func Seal(out, nonce, message, key []byte) []byte {
if len(nonce) != NonceSize {
panic("unsupported nonce size")
}
if len(key) != KeySize {
panic("unsupported key size")
}
var firstBlock [64]byte
cipher, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
cipher.XORKeyStream(firstBlock[:], firstBlock[:])
var polyKey [32]byte
copy(polyKey[:], firstBlock[:32])
ret, out := sliceForAppend(out, TagSize+len(message))
firstMessageBlock := message
if len(firstMessageBlock) > 32 {
firstMessageBlock = firstMessageBlock[:32]
}
tagOut := out
out = out[poly1305.TagSize:]
for i, x := range firstMessageBlock {
out[i] = firstBlock[32+i] ^ x
}
message = message[len(firstMessageBlock):]
ciphertext := out
out = out[len(firstMessageBlock):]
cipher.SetCounter(1)
cipher.XORKeyStream(out, message)
var tag [TagSize]byte
hash := poly1305.New(&polyKey)
hash.Write(ciphertext)
hash.Sum(tag[:0])
copy(tagOut, tag[:])
return ret
}
// Open does what the name suggests
func Open(out, nonce, box, key []byte) ([]byte, error) {
if len(nonce) != NonceSize {
panic("unsupported nonce size")
}
if len(key) != KeySize {
panic("unsupported key size")
}
if len(box) < TagSize {
return nil, errors.New("ciphertext is too short")
}
var firstBlock [64]byte
cipher, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
cipher.XORKeyStream(firstBlock[:], firstBlock[:])
var polyKey [32]byte
copy(polyKey[:], firstBlock[:32])
var tag [TagSize]byte
ciphertext := box[TagSize:]
hash := poly1305.New(&polyKey)
hash.Write(ciphertext)
hash.Sum(tag[:0])
if subtle.ConstantTimeCompare(tag[:], box[:TagSize]) != 1 {
return nil, errors.New("ciphertext authentication failed")
}
ret, out := sliceForAppend(out, len(ciphertext))
firstMessageBlock := ciphertext
if len(firstMessageBlock) > 32 {
firstMessageBlock = firstMessageBlock[:32]
}
for i, x := range firstMessageBlock {
out[i] = firstBlock[32+i] ^ x
}
ciphertext = ciphertext[len(firstMessageBlock):]
out = out[len(firstMessageBlock):]
cipher.SetCounter(1)
cipher.XORKeyStream(out, ciphertext)
return ret, nil
}
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
}