-
Notifications
You must be signed in to change notification settings - Fork 29
/
crypto.go
275 lines (240 loc) Β· 7.59 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
// crypto.go is an example to provides basic cryptographical functions for
// aeacus.
//
// This file is not a good example of cryptographic security. However, with this
// architecture of application (see security.md), it's good enough.
//
// Practically, it is more important that your implemented solution is different
// than the example, to make reverse engineering more difficult.
//
// You could change this file each time you release an image, which would make
// things more difficult for a would-be hacker.
//
// If you compile the source code yourself, using the Makefile, random strings
// will be generated for you. This means that the pre-compiled release will no
// longer work for decrypting your configs, which is good.
package main
import (
"bytes"
"compress/zlib"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
"strings"
)
// This string will be used for XORing the plaintext.
// Again-- not cryptographically genius.
//
// This string will be autogenerated if you run `make release`, or the shell
// script at `misc/dev/gen-crypto.sh`.
const (
randomString = "HASH_HERE"
)
// byteKey is used as the key for AES encryption. It will be autogenerated
// upon running `make release`, or the script at `misc/dev/gen-crypto.sh`.
var byteKey = []byte{0x01}
// randomBytes are used to obfuscate the config values. It will be auto-
// generated like the above two values.
var randomBytes = []byte{1}
// encryptConfig takes a plaintext string and returns an encrypted string that
// should be written to the encrypted scoring data file.
func encryptConfig(plainText string) (string, error) {
var key string
// If string is short, generate key by hashing string
if len(randomString) < 64 {
hasher := sha256.New()
_, err := hasher.Write([]byte(randomString))
if err != nil {
fail(err)
return "", err
}
key = string(hasher.Sum(nil))
} else {
key = randomString
}
// Compress the file with zlib
var compressedFile bytes.Buffer
writer := zlib.NewWriter(&compressedFile)
// Write zlib compressed data into encryptedFile
_, err := writer.Write([]byte(plainText))
if err != nil {
return "", err
}
writer.Close()
// XOR the file content with our key
xorConfig := xor(key, compressedFile.String())
// Return the AES-GCM encrypted file content
return encryptString(string(byteKey), xorConfig), nil
}
// decryptConfig is used to decrypt the scoring data file.
func decryptConfig(cipherText string) (string, error) {
var key string
// If string is short, generate key by hashing string
if len(randomString) < 64 {
hasher := sha256.New()
_, err := hasher.Write([]byte(randomString))
if err != nil {
fail(err)
return "", err
}
key = string(hasher.Sum(nil))
} else {
key = randomString
}
// Decrypt with AES-GCM and the byteKey
cipherText = decryptString(string(byteKey), cipherText)
// Apply the XOR key to get the zlib-compressed data.
cipherText = xor(key, cipherText)
// Create the zlib reader
reader, err := zlib.NewReader(bytes.NewReader([]byte(cipherText)))
if err != nil {
return "", errors.New("error creating zlib reader")
}
defer reader.Close()
// Read into our created buffer
dataBuffer := bytes.NewBuffer(nil)
// HACK
//
// For some reason, when we use zlib in combination with AES-GCM, zlib throws
// an unexpected EOF when the EOF is very expected. The hack right now is to
// just ignore errors if it's an unexpected EOF.
//
// Likely related: https://github.com/golang/go/issues/14675
_, err = io.Copy(dataBuffer, reader)
if err != nil {
if err.Error() == "unexpected EOF" {
debug("zlib returned unexpected EOF (expected error)")
err = nil
} else {
return "", errors.New("error decrypting or decompressing zlib data: " + err.Error())
}
}
// Sanity check that decryptedConfig is not empty
decryptedConfig := dataBuffer.String()
if decryptedConfig == "" {
return "", errors.New("decrypted config is empty")
}
return decryptedConfig, err
}
// tossKey is responsible for changing up the byteKey.
func tossKey() []byte {
// Add your cool byte array manipulations here!
return randomBytes
}
// obfuscateData encodes the configuration when writing to ScoringData. This
// also makes exposure of sensitive memory less likely, since there is a smaller
// window of opportunity for catching plaintext data.
func obfuscateData(datum *string) error {
var err error
if *datum == "" {
return nil
}
if *datum, err = encryptConfig(*datum); err == nil {
*datum = hexEncode(xor(string(tossKey()), *datum))
} else {
fail("crypto: failed to obufscate datum: " + err.Error())
return err
}
return nil
}
// deobfuscateData decodes configuration data.
func deobfuscateData(datum *string) error {
var err error
if *datum == "" {
// empty data given to deobfuscateData-- not really a concern often this
// is just empty/optional struct fields
return nil
}
*datum, err = hexDecode(*datum)
if err != nil {
fail("crypto: failed to deobfuscate datum hex: " + err.Error())
return err
}
*datum = xor(string(tossKey()), *datum)
if *datum, err = decryptConfig(*datum); err != nil {
fail("crypto: failed to deobfuscate datum: ", *datum, err.Error())
return err
}
return nil
}
// encryptString takes a password and a plaintext and returns an encrypted byte
// sequence (as a string). It uses AES-GCM with a 12-byte IV (as is
// recommended). The IV is prefixed to the string.
func encryptString(password, plainText string) string {
// Create a sha256sum hash of the password provided.
hasher := sha256.New()
_, err := hasher.Write([]byte(password))
if err != nil {
fail(err)
return ""
}
key := hasher.Sum(nil)
// Pad plainText to be a 16-byte block.
paddingArray := make([]byte, (aes.BlockSize - len(plainText)%aes.BlockSize))
for char := range paddingArray {
paddingArray[char] = 0x20 // Padding with space character.
}
plainText = plainText + string(paddingArray)
if len(plainText)%aes.BlockSize != 0 {
fail("plainText is not a multiple of block size!")
return ""
}
// Create cipher block with key.
block, err := aes.NewCipher(key)
if err != nil {
fail(err)
return ""
}
// Generate nonce.
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
fail(err)
return ""
}
// Create NewGCM cipher.
aesgcm, err := cipher.NewGCM(block)
if err != nil {
fail(err)
return ""
}
// Encrypt and seal plainText.
ciphertext := aesgcm.Seal(nil, nonce, []byte(plainText), nil)
ciphertext = []byte(fmt.Sprintf("%s%s", nonce, ciphertext))
return string(ciphertext)
}
// decryptString takes a password and a ciphertext and returns a decrypted
// byte sequence (as a string). The function uses typical AES-GCM.
func decryptString(password, ciphertext string) string {
// Create a sha256sum hash of the password provided.
hasher := sha256.New()
if _, err := hasher.Write([]byte(password)); err != nil {
fail(err)
}
key := hasher.Sum(nil)
// Grab the IV from the first 12 bytes of the file.
iv := []byte(ciphertext[:12])
ciphertext = ciphertext[12:]
// Create the AES block object.
block, err := aes.NewCipher(key)
if err != nil {
fail(err.Error())
return ""
}
// Create the AES-GCM cipher with the generated block.
aesgcm, err := cipher.NewGCM(block)
if err != nil {
fail("Error creating AES cipher (please tell the developers):", err.Error())
return ""
}
// Decrypt (and check validity, since it's GCM) of ciphertext.
plainText, err := aesgcm.Open(nil, iv, []byte(ciphertext), nil)
if err != nil {
fail("Error decrypting (are you using the correct aeacus/phocus? you may need to re-encrypt your config):", err.Error())
return ""
}
return strings.TrimSpace(string(plainText))
}