/* Encrypt a set of strings in AES CTR mode using the same nonce * Decrypt the resulting ciphertexts without the key or stream */ func c20() (actual, expected Result) { input, _ := ioutil.ReadFile("input/20.txt") output, _ := utils.ReadAndStripFile("output/20.txt") expected = string(output) strs := strings.Split(string(input), "\n") n := len(strs) strs = strs[:n-2] key := crypto.NewAesKey() nonce := uint64(0) // Decode strings and find length of the shortest string length := 1000 plaintexts := make([][]byte, len(strs)) for i, str := range strs { decoded, _ := base64.StdEncoding.DecodeString(str) if len(decoded) < length { length = len(decoded) } plaintexts[i] = decoded } var concat []byte for i, str := range plaintexts { // Truncate each string to common length plaintexts[i] = str[:length] stream, err := crypto.Ctr(nonce, key) if err != nil { log.Println(err) } // Concatenate the ciphertexts ciphertext := make([]byte, length) stream.XORKeyStream(ciphertext, plaintexts[i]) concat = append(concat, ciphertext...) } l := []int{length} plaintext, err := crypto.BreakXorRepeating(concat, l) if err != nil { log.Println(err) } return string(utils.Strip(plaintext)), expected }
/* CBC bitflipping attack * Create a function, that given an input, prepends: * "comment1=cooking%20MCs;userdata=" * and appends: * ";comment2=%20like%20a%20pound%20of%20bacon" * , quotes out the ; and =, and pads and encrypts it under AES CBC. * Another function should decrypt the string and return true if * ";admin=true;" exists in the string. Modify the ciphertext to * make the second funcion return true. */ func c16() (actual, expected Result) { key := crypto.NewAesKey() input := "XadminXtrue" inputBytes := []byte(input) str := profile.ProcessComment(input) iv, _ := bytes.Random(aes.BlockSize) encrypted, err := crypto.CbcEncrypt([]byte(str), key, iv) if err != nil { panic(err) } // Flip the targeted bytes ("X"s in the input string) encrypted[16] = encrypted[16] ^ 59 ^ inputBytes[0] encrypted[22] = encrypted[22] ^ 61 ^ inputBytes[6] hasAdmin := profile.HasAdmin(encrypted, key, iv) return hasAdmin, true }
/* Byte-at-a-time ECB decryption * Create a modified oracle function that decrypts an unknown string encrypted * under ECB-mode with a consistent, but unknown key. * AES-128-ECB(known-string || unknown-string, key) */ func c12() (actual, expected Result) { expected = "Rollin' in my 5.0\nWith my rag-top down so my hair can blow\nThe girlies on standby waving just to say hi\nDid you stop? No, I just drove by\n\x01" if crypto.GlobalAesKey == nil { crypto.GlobalAesKey = crypto.NewAesKey() } key := crypto.GlobalAesKey blocksize := crypto.DetectBlocksize(key) secretBlocks := len(crypto.AppendSecretEncryptEcb([]byte(""), key, false)) / blocksize var secret []byte createDict := func(plaintext []byte, block int) map[int][]byte { dict := make(map[int][]byte) for b := 0; b <= 255; b++ { extra := []byte{byte(b)} plaintext := append(plaintext, extra[0]) ciphertext := crypto.AppendSecretEncryptEcb(plaintext, key, false) dict[b] = ciphertext[block*blocksize : blocksize*(block+1)] } return dict } for n := 0; n < secretBlocks; n++ { for i := 0; i < blocksize; i++ { short := stdBytes.Repeat([]byte("A"), blocksize-(i+1)) plaintext := append(short, secret...) dict := createDict(plaintext, n) secretCiphertext := crypto.AppendSecretEncryptEcb(short, key, false) for char, lookup := range dict { if string(secretCiphertext[n*blocksize:blocksize*(n+1)]) == string(lookup) { char := []byte{byte(char)} secret = append(secret, char...) } } } } return string(secret), expected }
/* CBC padding oracle * Write a CBC padding oracle that decrypts a ciphertext and detects * if the plaintext is padded properly with PKCS#7. Choose a random line * from 17.txt, encrypt it, then decrypt it using the oracle. */ func c17() (actual, expected Result) { input, _ := ioutil.ReadFile("input/17.txt") strs := strings.Split(string(input), "\n") str := strs[r.Intn(10)] decodedStr, _ := base64.StdEncoding.DecodeString(str) if crypto.GlobalAesKey == nil { crypto.GlobalAesKey = crypto.NewAesKey() } key := crypto.GlobalAesKey iv, _ := bytes.Random(aes.BlockSize) ciphertext, err := crypto.CbcEncrypt([]byte(decodedStr), key, iv) if err != nil { log.Fatal(err) } blocks, err := bytes.SplitIntoBlocks(ciphertext, aes.BlockSize) if err != nil { log.Fatal(err) } var plaintext []byte for n := 0; n < len(blocks); n++ { block := blocks[n] controlled := make([]byte, aes.BlockSize) plaintextBlock := make([]byte, aes.BlockSize) intermediate := make([]byte, aes.BlockSize) prevBlock := make([]byte, aes.BlockSize) if n == 0 { prevBlock = iv } else { prevBlock = blocks[n-1] } for i := aes.BlockSize - 1; i >= 0; i-- { paddingLen := aes.BlockSize - i paddingByte := byte(paddingLen) // Set the last paddingLen bytes of controlled to so that when decrypted, // each will be a valid padding byte. for j := 0; j < paddingLen; j++ { controlled[i+j] = paddingByte ^ intermediate[i+j] } for b := 0; b <= 256; b++ { controlled[i] = byte(b) controlled := append(controlled, block...) valid, _ := crypto.CbcPaddingOracle(controlled, iv) if valid { // The padding is valid and we control the ith byte of the // block XORed with the intermediate state. XOR is an inverse // operation so finding the ith byte of the intermediate state // is as simple as: intermediate[i] = paddingByte ^ controlled[i] break } } plaintextBlock[i] = prevBlock[i] ^ intermediate[i] } plaintext = append(plaintext, plaintextBlock...) } decrypted, _ := crypto.CbcDecrypt(ciphertext, key, iv) return string(plaintext), string(decrypted) }
/* Byte-at-a-time ECB decryption * Same goal as #12, but prepend a random # of random bytes to input. * AES-128-ECB(random-#-bytes || input, key) */ func c14() (actual, expected Result) { expected = "Rollin' in my 5.0\nWith my rag-top down so my hair can blow\nThe girlies on standby waving just to say hi\nDid you stop? No, I just drove by\n\x01" if len(crypto.GlobalAesKey) == 0 { crypto.GlobalAesKey = crypto.NewAesKey() } key := crypto.GlobalAesKey blocksize := crypto.DetectBlocksize(key) createDict := func(plaintext []byte, prefixLength, block int) map[int][]byte { dict := make(map[int][]byte) for b := 0; b <= 255; b++ { extra := []byte{byte(b)} plaintext := append(plaintext, extra[0]) ciphertext := crypto.AppendSecretEncryptEcb(plaintext, key, true) dict[b] = ciphertext[(block*blocksize)+prefixLength : (blocksize*(block+1))+prefixLength] } return dict } findPrefixLength := func(blocksize int, key []byte) int { // If we send 2-3 blocks of repeating bytes, we will see a repeating block for i := blocksize * 2; i <= blocksize*3; i++ { encrypted := crypto.AppendSecretEncryptEcb(stdBytes.Repeat([]byte("A"), i), key, true) numBlocks := len(encrypted) / blocksize // Loop through blocks to find a repeat for j := 0; j < numBlocks-1; j++ { firstBlock := encrypted[j*blocksize : (j+1)*blocksize] secondBlock := encrypted[(j+1)*blocksize : (j+2)*blocksize] // Repeating block indicates we added enough bytes to make an even block if string(firstBlock) == string(secondBlock) { return (j+2)*blocksize - i } } } return 0 } // Knowing the length of the random prefix bytes, pad input to make a full block var secretBlocks int prefix := findPrefixLength(blocksize, key) // TODO: There must be a better way to account for prefix/16 rounding down and // giving 1 too few blocks if prefix%blocksize <= 5 { secretBlocks += 1 } pad := blocksize - (prefix % blocksize) // prefix + pad = full block prefix += pad // Figure out how many blocks to solve totalBlocks := len(crypto.AppendSecretEncryptEcb([]byte(""), key, true)) / blocksize secretBlocks += totalBlocks - (prefix / 16) var secret []byte for n := 0; n < secretBlocks; n++ { for i := 0; i < blocksize; i++ { // Send A*pad to pad the prefix to a full block, + A*blocksize-1, // blocksize-2, ... until that block of the secret is solved short := stdBytes.Repeat([]byte("A"), pad+blocksize-(i+1)) plaintext := append(short, secret...) // Create a dictionary of ciphertexts for every character dict := createDict(plaintext, prefix, n) secretCiphertext := crypto.AppendSecretEncryptEcb(short, key, true) for char, lookup := range dict { targetBlock := secretCiphertext[prefix+(n*blocksize) : prefix+(blocksize*(n+1))] if string(targetBlock) == string(lookup) { char := []byte{byte(char)} secret = append(secret, char...) } } } } return string(secret), expected }