-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathsymmetric.go
217 lines (197 loc) · 7.34 KB
/
symmetric.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package libdisco
import (
"crypto/rand"
"errors"
"github.com/mimoo/StrobeGo/strobe"
)
const (
nonceSize = 192 / 8
tagSize = 16
minimumCiphertextSize = nonceSize + tagSize
)
// Hash allows you to hash an input of any length and obtain an output of length greater or equal to 256 bits (32 bytes).
func Hash(input []byte, outputLength int) []byte {
if outputLength < 32 {
panic("disco: an output length smaller than 256-bit (32 bytes) has security consequences")
}
hash := strobe.InitStrobe("DiscoHash", 128)
hash.AD(false, input)
return hash.PRF(outputLength)
}
// DiscoHash represents a strobe hash
type DiscoHash struct {
strobeState strobe.Strobe
streaming bool
outputLength int
}
// NewHash creates a new hashing state, allowing you to absorb data to hash via Write or WriteTuple,
// and to create a digest via the Sum function.
func NewHash(outputLength int) DiscoHash {
if outputLength < 32 {
panic("disco: an output length smaller than 256-bit (32 bytes) has security consequences")
}
return DiscoHash{strobeState: strobe.InitStrobe("DiscoHash", 128), outputLength: outputLength}
}
// Write absorbs more data into the hash's state. This function is usually called to hash contigous chunks
// of data. For structured data please refer to WriteTuple
func (d *DiscoHash) Write(inputData []byte) (written int, err error) {
d.strobeState.Operate(false, "AD", inputData, 0, d.streaming)
d.streaming = true
written = len(inputData)
return
}
// WriteTuple absorbs more data to hash in a non-ambigious way. This means that data absorbed
// via this function is separated from the data surrounding it. Use this function instead of Write
// to hash structured data.
func (d *DiscoHash) WriteTuple(inputData []byte) (written int, err error) {
d.strobeState.Operate(false, "AD", inputData, 0, false)
written = len(inputData)
return
}
// Sum reads more output from the hash; reading affects the hash's
// state. (DiscoHash.Read is thus very different from Hash.Sum)
// It never returns an error.
func (d *DiscoHash) Sum() []byte {
reader := d.strobeState.Clone()
return reader.Operate(false, "PRF", nil, d.outputLength, false)
}
// Clone returns a copy of the DiscoHash in its current state.
func (d *DiscoHash) Clone() DiscoHash {
cloned := d.strobeState.Clone()
return DiscoHash{strobeState: *cloned}
}
// DeriveKeys allows you to derive keys
func DeriveKeys(inputKey []byte, outputLength int) []byte {
if len(inputKey) < 16 {
panic("disco: deriving keys from a value smaller than 128-bit (16 bytes) has security consequences")
}
hash := strobe.InitStrobe("DiscoKDF", 128)
hash.AD(false, inputKey)
return hash.PRF(outputLength)
}
// ProtectIntegrity allows you to send a message in cleartext (not encrypted)
// You can later verify via the VerifyIntegrity function that the message has not been modified
func ProtectIntegrity(key, plaintext []byte) []byte {
if len(key) < 16 {
panic("disco: using a key smaller than 128-bit (16 bytes) has security consequences")
}
hash := strobe.InitStrobe("DiscoMAC", 128)
hash.AD(false, key)
hash.AD(false, plaintext)
return append(plaintext, hash.Send_MAC(false, tagSize)...)
}
// VerifyIntegrity allows you to retrieve a message created with the ProtectIntegrity function.
// if it returns an error, it means that the message was altered. Otherwise it returns the original message.
func VerifyIntegrity(key, plaintextAndTag []byte) ([]byte, error) {
if len(key) < 16 {
panic("disco: using a key smaller than 128-bit (16 bytes) has security consequences")
}
if len(plaintextAndTag) < tagSize {
return nil, errors.New("disco: plaintext does not contain an integrity tag")
}
offset := len(plaintextAndTag) - tagSize
plaintext := plaintextAndTag[:offset]
tag := plaintextAndTag[offset:]
hash := strobe.InitStrobe("DiscoMAC", 128)
hash.AD(false, key)
hash.AD(false, plaintext)
// verify tag
if !hash.Recv_MAC(false, tag) {
return nil, errors.New("disco: the plaintext has been modified")
}
return plaintext, nil
}
// Encrypt allows you to encrypt a plaintext message with a key of any size greater than 128 bits (16 bytes).
func Encrypt(key, plaintext []byte) []byte {
if len(key) < 16 {
panic("disco: using a key smaller than 128-bit (16 bytes) has security consequences")
}
ae := strobe.InitStrobe("DiscoAE", 128)
// absorb the key
ae.AD(false, key)
// generate 192-bit nonce
var nonce [nonceSize]byte
_, err := rand.Read(nonce[:])
if err != nil {
panic("disco: golang's random function is not working")
}
// absorb the nonce
ae.AD(false, nonce[:])
// nonce + send_ENC(plaintext) + send_MAC(16)
ciphertext := append(nonce[:], ae.Send_ENC_unauthenticated(false, plaintext)...)
ciphertext = append(ciphertext, ae.Send_MAC(false, tagSize)...)
//
return ciphertext
}
// Decrypt allows you to decrypt a message that was encrypted with the Encrypt function.
func Decrypt(key, ciphertext []byte) ([]byte, error) {
if len(key) < 16 {
return nil, errors.New("disco: using a key smaller than 128-bit (16 bytes) has security consequences")
}
if len(ciphertext) < minimumCiphertextSize {
return nil, errors.New("disco: ciphertext is too small, it should contain at a minimum a 192-bit nonce and a 128-bit tag")
}
// instantiate
ae := strobe.InitStrobe("DiscoAE", 128)
// absorb the key
ae.AD(false, key)
// absorb the nonce
ae.AD(false, ciphertext[:nonceSize])
// decrypt
plaintext := ae.Recv_ENC_unauthenticated(false, ciphertext[nonceSize:len(ciphertext)-tagSize])
// verify tag
ok := ae.Recv_MAC(false, ciphertext[len(ciphertext)-tagSize:])
if !ok {
return nil, errors.New("disco: cannot decrypt the payload")
}
return plaintext, nil
}
// EncryptAndAuthenticate allows you to encrypt a plaintext message with a key of any size greater than 128 bits (16 bytes).
func EncryptAndAuthenticate(key, plaintext, ad []byte) []byte {
if len(key) < 16 {
panic("disco: using a key smaller than 128-bit (16 bytes) has security consequences")
}
ae := strobe.InitStrobe("DiscoAEAD", 128)
// absorb the key
ae.AD(false, key)
// absorb the AD
ae.AD(false, ad)
// generate 192-bit nonce
var nonce [nonceSize]byte
_, err := rand.Read(nonce[:])
if err != nil {
panic("disco: golang's random function is not working")
}
// absorb the nonce
ae.AD(false, nonce[:])
// nonce + send_ENC(plaintext) + send_MAC(16)
ciphertext := append(nonce[:], ae.Send_ENC_unauthenticated(false, plaintext)...)
ciphertext = append(ciphertext, ae.Send_MAC(false, tagSize)...)
//
return ciphertext
}
// DecryptAndAuthenticate allows you to decrypt a message that was encrypted with the Encrypt function.
func DecryptAndAuthenticate(key, ciphertext, ad []byte) ([]byte, error) {
if len(key) < 16 {
return nil, errors.New("disco: using a key smaller than 128-bit (16 bytes) has security consequences")
}
if len(ciphertext) < minimumCiphertextSize {
return nil, errors.New("disco: ciphertext is too small, it should contain at a minimum a 192-bit nonce and a 128-bit tag")
}
// instantiate
ae := strobe.InitStrobe("DiscoAEAD", 128)
// absorb the key
ae.AD(false, key)
// absorb the AD
ae.AD(false, ad)
// absorb the nonce
ae.AD(false, ciphertext[:nonceSize])
// decrypt
plaintext := ae.Recv_ENC_unauthenticated(false, ciphertext[nonceSize:len(ciphertext)-tagSize])
// verify tag
ok := ae.Recv_MAC(false, ciphertext[len(ciphertext)-tagSize:])
if !ok {
return nil, errors.New("disco: cannot decrypt the payload")
}
return plaintext, nil
}