-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathadiantum.go
116 lines (103 loc) · 3.19 KB
/
adiantum.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
package adiantum // import "lukechampine.com/adiantum"
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"math/bits"
"golang.org/x/crypto/poly1305"
"lukechampine.com/adiantum/hbsh"
"lukechampine.com/adiantum/internal/xchacha"
"lukechampine.com/adiantum/nh"
)
// hashNHPoly1305 implements hbsh.Hash with NH and Poly1305.
type hashNHPoly1305 struct {
keyT [32]byte
keyM [32]byte
keyNH [1072]byte
}
// Sum implements hbsh.Hash.
func (h *hashNHPoly1305) Sum(dst, msg, tweak []byte) []byte {
// poly1305 hash 8*len(msg) and tweak with keyT
tweakBuf := make([]byte, 16+24)
binary.LittleEndian.PutUint64(tweakBuf[:8], uint64(8*len(msg)))
var outT [16]byte
poly1305.Sum(&outT, append(tweakBuf[:16], tweak...), &h.keyT)
// NH hash message in chunks of up to 1024 bytes, then poly1305 those hashes
// with keyM
mac := poly1305.New(&h.keyM)
var outNH [32]byte
for len(msg) >= 1024 {
nh.Sum(&outNH, msg[:1024], h.keyNH[:])
mac.Write(outNH[:])
msg = msg[1024:]
}
// handle final (incomplete) chunk, if it exists
if len(msg) > 0 {
// if necessary, pad to multiple of 16 bytes
if len(msg)%16 != 0 {
var pad [1024]byte
n := copy(pad[:], msg)
n += 16 - (n % 16)
msg = pad[:n]
}
nh.Sum(&outNH, msg, h.keyNH[:])
mac.Write(outNH[:])
}
var outM [16]byte
mac.Sum(outM[:0])
// return the sum of the hashes
sum := addHashes(outT, outM)
return append(dst[:0], sum[:]...)
}
type chachaStream struct {
key []byte
rounds int
}
func (s *chachaStream) XORKeyStream(msg, nonce []byte) {
nonceBuf := make([]byte, 24)
n := copy(nonceBuf, nonce)
nonceBuf[n] = 1
xchacha.XORKeyStream(msg, msg, nonceBuf, s.key, s.rounds)
}
func makeAdiantum(key []byte, chachaRounds int) (hbsh.StreamCipher, cipher.Block, hbsh.TweakableHash) {
if len(key) != xchacha.KeySize {
panic("adiantum: key must be 32 bytes long")
}
// create stream cipher and derive block+hash keys
stream := &chachaStream{key, chachaRounds}
keyBuf := bytes.NewBuffer(make([]byte, 32+16+16+1072))
stream.XORKeyStream(keyBuf.Bytes(), nil)
block, _ := aes.NewCipher(keyBuf.Next(32))
hash := new(hashNHPoly1305)
copy(hash.keyT[:16], keyBuf.Next(16))
copy(hash.keyM[:16], keyBuf.Next(16))
copy(hash.keyNH[:], keyBuf.Next(1072)) // enough to hash a 1024-byte message
return stream, block, hash
}
// New8 returns an Adiantum cipher with the specified key, using XChaCha8 as the
// stream cipher. The key must be 32 bytes.
func New8(key []byte) *hbsh.HBSH {
return hbsh.New(makeAdiantum(key, 8))
}
// New returns an Adiantum cipher with the specified key. The key must be 32
// bytes.
func New(key []byte) *hbsh.HBSH {
return hbsh.New(makeAdiantum(key, 12))
}
// New20 returns an Adiantum cipher with the specified key, using XChaCha20 as
// the stream cipher. The key must be 32 bytes.
func New20(key []byte) *hbsh.HBSH {
return hbsh.New(makeAdiantum(key, 20))
}
func addHashes(x, y [16]byte) [16]byte {
x1 := binary.LittleEndian.Uint64(x[:8])
x2 := binary.LittleEndian.Uint64(x[8:16])
y1 := binary.LittleEndian.Uint64(y[:8])
y2 := binary.LittleEndian.Uint64(y[8:16])
r1, c := bits.Add64(x1, y1, 0)
r2, _ := bits.Add64(x2, y2, c)
binary.LittleEndian.PutUint64(x[:8], r1)
binary.LittleEndian.PutUint64(x[8:], r2)
return x
}