-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathset2_test.go
488 lines (386 loc) · 13.2 KB
/
set2_test.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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
package cryptopals
/*
## Cryptopals Solutions by Mohit Muthanna Cheppudira 2020.
This file consists of solutions to Set 2. Run with:
$ go test -v --run S2
*/
import (
"bytes"
"encoding/base64"
"fmt"
"io/ioutil"
"math/rand"
"regexp"
"strings"
"testing"
"time"
)
func TestS2C9(t *testing.T) {
plainText := "YELLOW SUBMARINE"
want := "YELLOW SUBMARINE\x04\x04\x04\x04"
paddedText, err := padPKCS7(plainText, 20)
assertNoError(t, err)
assertEquals(t, want, paddedText)
}
func TestS2C10(t *testing.T) {
plainText := []byte("YELLOW SUBMARINEYELLOW SUBMARINEYELLOW SUBMARINEYELLOW SUBMARINE")
key := []byte("1234567890ABCDEF")
iv := make([]byte, 16)
cipherText, err := encryptAESCBC(plainText, key, iv)
assertNoError(t, err)
newPlainText, err := decryptAESCBC(cipherText, key, iv)
assertNoError(t, err)
assertEquals(t, string(plainText), string(newPlainText))
data, err := ioutil.ReadFile("data/10.txt")
assertNoError(t, err)
cipherText, err = base64.StdEncoding.DecodeString(string(data))
plainText, err = decryptAESCBC(cipherText, []byte("YELLOW SUBMARINE"), iv)
assertNoError(t, err)
trimmedPlaintext := strings.Trim(string(plainText), "\x04\n ")
re := regexp.MustCompile(`Play that funky music$`)
assertEquals(t, true, re.MatchString(trimmedPlaintext))
}
func TestECBEncryptDecrypt(t *testing.T) {
plainText := make([]byte, 16*100)
_, err := rand.Read(plainText)
assertNoError(t, err)
key := make([]byte, 16)
_, err = rand.Read(key)
assertNoError(t, err)
cipherText, err := encryptAESECB(plainText, key, 16)
assertNoError(t, err)
newPlainText, err := decryptAESECB(cipherText, key, 16)
assertNoError(t, err)
assertEquals(t, true, bytes.Equal(plainText, newPlainText))
}
func TestS2C11(t *testing.T) {
detectBlockSize := func(data []byte) (int, error) {
bestBlockSize := 0
for i := 4; i <= 40; i++ {
distance, err := numSimilarBlocks(data, i, 4)
fmt.Println(i, distance)
if err != nil {
return 0, fmt.Errorf("could not calculate block distance: %w", err)
}
// Pick the largest block size with similar blocks. This is due to aliasing
// effects of similarity. E.g., block size 16 with 1 similar block will have
// block size 8 with 2 similar blocks.
//
// Fixme: use square for similar block size
if distance > 0 {
bestBlockSize = i
}
}
return bestBlockSize, nil
}
rand.Seed(time.Now().UnixNano())
plainText, err := ioutil.ReadFile("data/11.txt")
assertNoError(t, err)
cipherText, err := encryptAESRandom(plainText)
assertNoError(t, err)
similarity, err := numSimilarBlocks(cipherText, 16, 0)
assertNoError(t, err)
detectedBlockSize, err := detectBlockSize(cipherText)
assertNoError(t, err)
// If there are any similar blocks, then this is ECB
if similarity > 0 {
fmt.Println(similarity, "ECB", detectedBlockSize)
} else {
fmt.Println(similarity, "CBC", detectedBlockSize)
}
}
func TestS2C12(t *testing.T) {
rand.Seed(time.Now().UnixNano())
key := make([]byte, 16)
_, err := rand.Read(key)
assertNoError(t, err)
secretData, err := ioutil.ReadFile("data/12.txt")
assertNoError(t, err)
secret, err := base64.StdEncoding.DecodeString(string(secretData))
assertNoError(t, err)
// Encryption oracle
encrypt := func(plainText []byte) ([]byte, error) {
newPlainText := append(plainText, secret...)
newPlainText, err = padPKCS7ToBlockSize(newPlainText, 16)
if err != nil {
return nil, fmt.Errorf("could not PKCS7 pad: %w", err)
}
cipherText, err := encryptAESECB(newPlainText, key, 16)
if err != nil {
return nil, fmt.Errorf("could not ECB encrypt: %w", err)
}
return cipherText, nil
}
isECB, blockSize, err := detectAESECB(encrypt)
assertNoError(t, err)
assertEquals(t, true, isECB)
assertEquals(t, 16, blockSize)
// Figure out length of secret
cipherText, err := encrypt([]byte{})
assertNoError(t, err)
crackedSecret, err := crackAESECB(encrypt, len(cipherText))
assertNoError(t, err)
fmt.Println(string(crackedSecret))
assertEquals(t, bytes.Equal(secret, crackedSecret[:len(secret)]), true)
}
// parseCookie extracts the "key=value&key=value" pairs from 'cookie' and
// returns a map. This is used by a number of challenges in set 2.
func parseCookie(cookie string) (map[string]string, error) {
parts := strings.Split(cookie, "&")
cookieMap := map[string]string{}
for _, part := range parts {
subParts := strings.Split(part, "=")
if len(subParts) != 2 {
return nil, fmt.Errorf("Invalid cookie part %s in %s", part, cookie)
}
cookieMap[strings.TrimSpace(subParts[0])] = subParts[1]
}
return cookieMap, nil
}
func TestCookieParsing(t *testing.T) {
cookie, err := parseCookie("foo=bar&baz=qux&zap=zazzle")
assertNoError(t, err)
assertEquals(t, "qux", cookie["baz"])
cookie, err = parseCookie("foo=bar&baz&=qux&zap=zazzle")
assertHasError(t, err)
cookie, err = parseCookie("foo =bar&baz=qux&zap=zazzle")
assertNoError(t, err)
assertEquals(t, "bar", cookie["foo"])
}
func TestS2C13(t *testing.T) {
// Remove cookie control characters '&' and '&' from val.
sanitizeCookieValue := func(val string) string {
sanitizedString := ""
for _, c := range val {
if c != '&' && c != '=' {
sanitizedString += string(c)
}
}
return sanitizedString
}
// Verify that sanitization works
sanitized := sanitizeCookieValue("fo==obar&&boo=baz&hello")
assertEquals(t, "foobarboobazhello", sanitized)
// Encoodes cookie map into a string
encodeCookie := func(cookie map[string]string, order []string) string {
cookies := []string{}
for _, k := range order {
cookies = append(cookies, fmt.Sprintf("%s=%s", sanitizeCookieValue(k), sanitizeCookieValue(cookie[k])))
}
return strings.Join(cookies, "&")
}
// Returns the encoded profile for email
profileFor := func(email string) string {
profile := map[string]string{
"email": email,
"uid": "10",
"role": "user",
}
return encodeCookie(profile, []string{"email", "uid", "role"})
}
assertEquals(t, "[email protected]&uid=10&role=user", profileFor("[email protected]"))
rand.Seed(time.Now().UnixNano())
key := make([]byte, 16)
_, err := rand.Read(key)
assertNoError(t, err)
encrypt := func(plainText []byte) ([]byte, error) {
newPlainText, err := padPKCS7ToBlockSize(plainText, 16)
if err != nil {
return nil, fmt.Errorf("could not PKCS7 pad: %w", err)
}
cipherText, err := encryptAESECB(newPlainText, key, 16)
if err != nil {
return nil, fmt.Errorf("could not ECB encrypt: %w", err)
}
return cipherText, nil
}
decrypt := func(cipherText []byte) ([]byte, error) {
plainText, err := decryptAESECB(cipherText, key, 16)
if err != nil {
return nil, fmt.Errorf("could not ECB decrypt: %w", err)
}
newPlainText, err := unpadPKCS7(plainText)
if err != nil {
return nil, fmt.Errorf("could not PKCS7 pad: %w", err)
}
return newPlainText, nil
}
// Verify that encrypt/decrypt works correctly
plainText := []byte(profileFor("[email protected]"))
cipherText, err := encrypt(plainText)
assertNoError(t, err)
newPlainText, err := decrypt(cipherText)
assertNoError(t, err)
assertTrue(t, bytes.Equal(plainText, newPlainText))
// Verify that parseCookie works correctly
profile, err := parseCookie(string(newPlainText))
assertNoError(t, err)
assertEquals(t, profile["email"], "[email protected]")
assertEquals(t, profile["uid"], "10")
assertEquals(t, profile["role"], "user")
// Construct email to push role to new independent block
plainText = []byte(profileFor("[email protected]"))
cipherText, err = encrypt(plainText)
assertNoError(t, err)
assertEquals(t, 64, len(cipherText))
// Verify that last block is just "user" (role)
userRoleCipherText, err := encrypt([]byte("user"))
assertNoError(t, err)
assertTrue(t, bytes.Equal(userRoleCipherText, cipherText[48:]))
// Create an encrypted block with the contents "admin"
adminRoleCipherText, err := encrypt([]byte("admin"))
assertNoError(t, err)
// Finally, replace the "user" block with the "admin" block
finalCipherText := append(cipherText[:48], adminRoleCipherText...)
finalPlainText, err := decrypt(finalCipherText)
// Verify that the final cipher text is a user with the admin role
finalProfile, err := parseCookie(string(finalPlainText))
assertNoError(t, err)
fmt.Println(finalProfile)
assertEquals(t, "admin", finalProfile["role"])
}
func TestS2C14(t *testing.T) {
rand.Seed(time.Now().UnixNano())
key := make([]byte, 16)
_, err := rand.Read(key)
assertNoError(t, err)
// Use the same target bytes as C12
secretData, err := ioutil.ReadFile("data/12.txt")
assertNoError(t, err)
secret, err := base64.StdEncoding.DecodeString(string(secretData))
assertNoError(t, err)
numBytes := rand.Intn(10) + 1
randomPrefix := make([]byte, numBytes)
_, err = rand.Read(randomPrefix)
assertNoError(t, err)
// Encryption oracle
encrypt := func(plainText []byte) ([]byte, error) {
newPlainText := append(randomPrefix, append(plainText, secret...)...)
newPlainText, err = padPKCS7ToBlockSize(newPlainText, 16)
if err != nil {
return nil, fmt.Errorf("could not PKCS7 pad: %w", err)
}
cipherText, err := encryptAESECB(newPlainText, key, 16)
if err != nil {
return nil, fmt.Errorf("could not ECB encrypt: %w", err)
}
return cipherText, nil
}
isECB, blockSize, err := detectAESECB(encrypt)
assertNoError(t, err)
assertEquals(t, true, isECB)
assertEquals(t, 16, blockSize)
// Determine length of random text
randomTextLen := 0
plainText := make([]byte, 0, blockSize)
cipherText, err := encrypt(plainText)
assertNoError(t, err)
for i := 1; i <= blockSize; i++ {
plainText = append(plainText, '\x00')
cipherBlock, err := encrypt(plainText)
assertNoError(t, err)
if bytes.Equal(cipherBlock[:blockSize], cipherText[:blockSize]) {
randomTextLen = blockSize - (i - 1)
break
}
cipherText = cipherBlock
}
spacing := blockSize - randomTextLen
// Crack ECB byte-at-a-time
crackedSecret, err := crackAESECB(encrypt, (blockSize*11)+spacing)
assertNoError(t, err)
fmt.Println(string(crackedSecret))
}
func TestS2C15(t *testing.T) {
text, err := unpadPKCS7([]byte("ICE ICE BABY\x04\x04\x04\x04"))
assertNoError(t, err)
assertTrue(t, bytes.Equal([]byte("ICE ICE BABY"), text))
text, err = unpadPKCS7([]byte("ICE ICE BABY\x05\x05\x05\x05"))
assertHasError(t, err)
text, err = unpadPKCS7([]byte("ICE ICE BABY\x01\x02\x03\x04"))
assertHasError(t, err)
text, err = unpadPKCS7([]byte("ICE ICE BABY\x03\x03\x03\x03"))
assertHasError(t, err)
// Should fail because PKCS7 requires that text of blockSize length have
// an extra block added.
text, err = unpadPKCS7([]byte("0123456789ABCDEF"))
assertHasError(t, err)
// Pad to verify that unpadding works correctly
paddedText, err := padPKCS7ToBlockSize([]byte("0123456789ABCDEF"), 16)
assertNoError(t, err)
assertEquals(t, 32, len(paddedText))
text, err = unpadPKCS7(paddedText)
assertNoError(t, err)
}
func TestS2C16(t *testing.T) {
rand.Seed(time.Now().UnixNano())
key := make([]byte, 16)
_, err := rand.Read(key)
assertNoError(t, err)
iv := make([]byte, 16)
_, err = rand.Read(iv)
assertNoError(t, err)
encrypt := func(input []byte) ([]byte, error) {
pre := []byte("comment1=cooking%20MCs;userdata=")
post := []byte(";comment2=%20like%20a%20pound%20of%20bacon")
sanitizedInput := []byte{}
for _, c := range input {
if c != ';' && c != '=' {
sanitizedInput = append(sanitizedInput, c)
}
}
plainText := append(pre, append(sanitizedInput, post...)...)
plainText, err = padPKCS7ToBlockSize(plainText, 16)
if err != nil {
return nil, fmt.Errorf("could not PKCS7 pad: %w", err)
}
cipherText, err := encryptAESCBC(plainText, key, iv)
if err != nil {
return nil, fmt.Errorf("could not CBC encrypt: %w", err)
}
return cipherText, nil
}
decrypt := func(cipherText []byte) ([]byte, error) {
plainText, err := decryptAESCBC(cipherText, key, iv)
if err != nil {
return nil, fmt.Errorf("could not CBC decrypt: %w", err)
}
plainText, err = unpadPKCS7(plainText)
if err != nil {
return nil, fmt.Errorf("could not PKCS7 unpad: %w", err)
}
return plainText, nil
}
isCracked := func(cipherText []byte) bool {
plainText, err := decrypt(cipherText)
assertNoError(t, err)
match, err := regexp.MatchString(";admin=true;", string(plainText))
assertNoError(t, err)
return match
}
cipherText, err := encrypt([]byte(";admin=true;"))
assertNoError(t, err)
assertFalse(t, isCracked(cipherText))
zeroBlock := make([]byte, 16)
adminBlock := []byte(";admin=true;")
// Flips a single bit in data, indexed by byteIndex and bitIndex
flipBit := func(data []byte, byteIndex int, bitIndex int) {
data[byteIndex] ^= byte((1 << 7) >> bitIndex)
}
// Flip the bits for ; and = so that the sanitizer passes them through.
flipBit(adminBlock, 0, 7)
flipBit(adminBlock, 6, 7)
flipBit(adminBlock, 11, 7)
// Construct cipher text with zero block and our custom admin block. The idea
// is to flip bits in the zero block, such that they are carried over to the
// admin block.
cipherText, err = encrypt(append(zeroBlock, adminBlock...))
assertNoError(t, err)
// Flip bits in the zero block of cipherText. This completely scrambles the zero block
// but only propagates single-bit errors to the next block(s).
flipBit(cipherText, 32, 7)
flipBit(cipherText, 32+6, 7)
flipBit(cipherText, 32+11, 7)
// Verify that we now have ";admin=true" in our cipherText
assertTrue(t, isCracked(cipherText))
}