-
Notifications
You must be signed in to change notification settings - Fork 9
/
crypto.go
283 lines (246 loc) · 8.3 KB
/
crypto.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
package handshake
import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"time"
multihash "github.com/multiformats/go-multihash"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/nacl/secretbox"
)
const (
// secretBoxDefaultChunkSize is the default size of an encrypted chunk of data
secretBoxDefaultChunkSize = 16000
// secretBoxDecryptionOffset is the additional offset of bytes needed to offset
// for the nonce and authentication bytes
secretBoxDecryptionOffset = 40
// secretBoxNonceLength is the length in bytes required for the nonce
secretBoxNonceLength = 24
// secretBoxKeyLength is the length in bytes required for the key
secretBoxKeyLength = 32
blake2b256code = uint64(45600)
blake2b256length = 32
blake2b256name = "blake2b-256"
lookupHashLength = 24
)
// NonceType is used for type enumeration for Ciphers Nonces
type NonceType int
// CipherType is used for type enumeration of Ciphers
type CipherType int
const (
// RandomNonce is the NonceType used for pure crypto/rand generated nonces
RandomNonce NonceType = iota
// TimeSeriesNonce is the NonceType used for 4 byte unix time prefixed crypto/rand generated nonces
TimeSeriesNonce
)
const (
// SecretBox is a CipherType
SecretBox CipherType = iota
)
// Cipher is an interface used for encrypting and decrypting byte slices.
type cipher interface {
Encrypt(data []byte, key []byte) ([]byte, error)
Decrypt(data []byte, key []byte) ([]byte, error)
share() (peerCipher, error)
export() (cipherConfig, error)
}
// peerCipher is a struct used to share cipher settings to a peer in handshake
type peerCipher struct {
Type CipherType `json:"type"`
ChunkSize int `json:"chunk_size,omitempty"`
}
// cipherConfig is a struct used to share cipher settings to a peer in handshake
type cipherConfig struct {
Type CipherType
ChunkSize int
}
// genRandBytes takes a length of l and returns a byte slice of random data
func genRandBytes(l int) []byte {
b := make([]byte, l)
rand.Read(b)
return b
}
// genLookups takes a pepper and entropy []byte, a CipherType, and a count and returns a map[string][]byte for lookup hashes
func genLookups(pepper [64]byte, entropy [96]byte, cipherType CipherType, count int) (lookup, error) {
lookups := make(map[string][]byte)
if count < 1 {
return lookups, errors.New("count must be greater than or equal to 1")
}
p, e1, e2, e3 := pepper[:], entropy[:32], entropy[32:64], entropy[64:]
var keyLength int
switch cipherType {
case SecretBox:
keyLength = secretBoxKeyLength
default:
return lookups, fmt.Errorf("cipher type %v is not implemented for lookup generation", cipherType)
}
lookupBytes := argon2.IDKey(p, e2, 1, 64*1024, 4, uint32(count*lookupHashLength))
keyBytes := argon2.IDKey(e1, e3, 1, 64*1024, 4, uint32(count*keyLength))
for i := 1; i < count; i++ {
lookupStart := (i - 1) * lookupHashLength
lookupEnd := i * lookupHashLength
keyStart := (i - 1) * keyLength
keyEnd := i * keyLength
k := base64.StdEncoding.EncodeToString(lookupBytes[lookupStart:lookupEnd])
v := keyBytes[keyStart:keyEnd]
lookups[k] = v
}
return lookups, nil
}
// base58Multihash a set of bytes to an IPFS style blake2b-256 multihash in base58 encoding
func base58Multihash(b []byte) string {
mh, _ := multihash.Sum(b, blake2b256code, blake2b256length)
return mh.B58String()
}
// isHashmapMultihash takes a string encoded base58 multihash and checks to see if it is supported
// by handshake. Currently, handshake only supports
func isHashmapMultihash(hash string) bool {
mh, err := multihash.FromB58String(hash)
if err != nil {
return false // return false if error decoding
}
decoded, err := multihash.Decode(mh)
if err != nil {
return false
}
switch decoded.Name {
case blake2b256name:
return true
}
return false
}
// genTimeStampNonce takes an int for the nonce size and returns a byte slice of length size.
// A byte slice is created for the nonce and filled with random data from `crypto/rand`, then the
// first 4 bytes of the nonce are overwritten with LittleEndian encoding of `time.Now().Unix()`
// The purpose of this function is to avoid an unlikely collision in randomly generating nonces
// by prefixing the nonce with time series data.
func genTimeStampNonce(l int) []byte {
nonce := genRandBytes(l)
timeBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(timeBytes, uint64(time.Now().Unix()))
copy(nonce, timeBytes[:4])
return nonce
}
// DeriveKey takes a password and salt and applies a set of fixed parameters
// to the argon2 IDKey algorithm.
func deriveKey(pw, salt []byte) []byte {
return argon2.IDKey(pw, salt, 1, 64*1024, 4, secretBoxKeyLength)
}
// SecretBoxCipher is a struct and method set that conforms to the Cipher interface. This is the primary cipher used
// for all blob encryption and decryption for handshake
type SecretBoxCipher struct {
Nonce NonceType
ChunkSize int
}
// newTimeSeriesSBCipher returns a timeSeriesNonce based SecretBoxCipher struct that conforms to the
// Cipher interface
func newTimeSeriesSBCipher() SecretBoxCipher {
return SecretBoxCipher{Nonce: TimeSeriesNonce, ChunkSize: secretBoxDefaultChunkSize}
}
func newDefaultCipher() SecretBoxCipher {
return newDefaultSBCipher()
}
// newDefaultSBCipher returns a RandomNonce based SecretBoxCipher struct that conforms to the
// Cipher interface
func newDefaultSBCipher() SecretBoxCipher {
return SecretBoxCipher{Nonce: RandomNonce, ChunkSize: secretBoxDefaultChunkSize}
}
// Encrypt takes byte slices for data and a key and returns the ciphertext output for secretbox
func (s SecretBoxCipher) Encrypt(data []byte, key []byte) ([]byte, error) {
var encryptedData []byte
chunkSize := s.ChunkSize
if len(key) != secretBoxKeyLength {
return encryptedData, errors.New("invalid key length")
}
var k [secretBoxKeyLength]byte
copy(k[:], key)
for i := 0; i < len(data); i = i + chunkSize {
var chunk []byte
if len(data[i:]) >= chunkSize {
chunk = data[i : i+chunkSize]
} else {
chunk = data[i:]
}
nonce := s.genNonce()
var n [secretBoxNonceLength]byte
copy(n[:], nonce)
encryptedChunk := secretbox.Seal(n[:], chunk, &n, &k)
encryptedData = append(encryptedData, encryptedChunk...)
}
return encryptedData, nil
}
// Decrypt takes byte slices for data and key and returns the clear text output for secretbox
func (s SecretBoxCipher) Decrypt(data []byte, key []byte) ([]byte, error) {
var decryptedData []byte
chunkSize := s.ChunkSize + secretBoxDecryptionOffset
if len(key) != secretBoxKeyLength {
return decryptedData, errors.New("invalid key length")
}
var k [secretBoxKeyLength]byte
copy(k[:], key)
for i := 0; i < len(data); i = i + chunkSize {
var chunk []byte
if len(data[i:]) >= chunkSize {
chunk = data[i : i+chunkSize]
} else {
chunk = data[i:]
}
var n [secretBoxNonceLength]byte
copy(n[:], chunk[:secretBoxNonceLength])
decryptedChunk, ok := secretbox.Open(nil, chunk[secretBoxNonceLength:], &n, &k)
if !ok {
return nil, errors.New("decrypt failed")
}
decryptedData = append(decryptedData, decryptedChunk...)
}
return decryptedData, nil
}
// GenNonce returns a set of nonce bytes based on the NonceType configured in the struct
func (s SecretBoxCipher) genNonce() []byte {
switch s.Nonce {
case RandomNonce:
return genRandBytes(secretBoxNonceLength)
case TimeSeriesNonce:
return genTimeStampNonce(secretBoxNonceLength)
default:
return genRandBytes(secretBoxNonceLength)
}
}
// share is used to export settings shared with a peer
func (s SecretBoxCipher) share() (peerCipher, error) {
return peerCipher{
Type: SecretBox,
ChunkSize: secretBoxDefaultChunkSize,
}, nil
}
// export is used to export settings shared with a peer
func (s SecretBoxCipher) export() (cipherConfig, error) {
return cipherConfig{
Type: SecretBox,
ChunkSize: secretBoxDefaultChunkSize,
}, nil
}
func newCipherFromPeer(config peerCipher) (c cipher, err error) {
switch config.Type {
case SecretBox:
return SecretBoxCipher{
Nonce: RandomNonce,
ChunkSize: config.ChunkSize,
}, nil
default:
return c, errors.New("cipher not implemented for config import")
}
}
func newCipherFromConfig(config cipherConfig) (c cipher, err error) {
switch config.Type {
case SecretBox:
return SecretBoxCipher{
Nonce: RandomNonce,
ChunkSize: config.ChunkSize,
}, nil
default:
return c, errors.New("cipher not implemented for config import")
}
}