This post is based on the “Recover the key from CBC with IV=Key” exercise from Cryptopals. I highly recommend attempting the previous CBC exercises yourself as they do a great job ramping up your knowledge on the subject.

## Prerequisites

In order for this attack to be successful two things must be in place:

- The encryption process must use the
`KEY`

as the`IV`

- The server throws an error when decryption fails and reflects the decoded message to the attacker

## Attack

Given the conditions above we can exploit this in the following way:

- Make a plaintext with a length of
*at least*3 blocks - Encrypt the plaintext and get the resulting ciphertext
- Modify the second block of the ciphertext to
*contain only zeros* - Modify the third block of the ciphertext to be
*the same as the first block* - Decrypt the ciphertext and get the
*invalid plaintext*result - XOR the first and third blocks of the
*invalid plaintext* - That’s our key!

## Explanation

This seems magical at first, but let’s review the algorithm that is performed by CBC during decryption:

Deconstructing what needs to happen in order to decrypt our `first block`

:

`result = AES_Decrypt(first_block_ciphertext, KEY)`

`result XOR KEY`

(remember that we are using the KEY as the IV)

Deconstructing what needs to happen in order to decrypt our `third block`

:

`result = AES_Decrypt(third_block_ciphertext, KEY)`

`result XOR second_block_ciphertext`

Let’s `XOR`

these operations together:

```
AES_Decrypt(first_block_ciphertext, KEY) XOR KEY
XOR
AES_Decrypt(third_block_ciphertext, KEY) XOR second_block_ciphertext
```

Given that our first ciphertext block **is the same** as our third ciphertext block (step 4 of attack) we know that the following operations will produce the same result:

`AES_Decrypt(first_block_ciphertext, KEY)`

`AES_Decrypt(third_block_ciphertext, KEY)`

When we XOR these two operations together the result will be **zero**. This leaves us with:

```
=> 0 XOR KEY XOR second_block_ciphertext
=> KEY XOR second_block_ciphertext
```

Remember that we made our second ciphertext block contain only zeroes (step 3 of attack), so this becomes:

```
=> KEY XOR 0
=> KEY
```

And that’s the reason we can extract the `KEY`

using this algorithm.

## Implementation based on Cryptopal’s requirements

```
class InvalidFormat < StandardError; end
KEY = 16.times.map { rand(0..255) }
def encode_cookie(input)
prefix = 'comment1=cooking%20MCs;userdata='
suffix = ';comment2=%20like%20a%20pound%20of%20bacon'
plaintext = prefix + input.tr(';=', '') + suffix
raise InvalidFormat.new(plaintext) unless plaintext.ascii_only?
aes_cbc_encrypt(pkcs7_pad(plaintext.bytes, 16), KEY, KEY)
end
def decode_cookie(ciphertext)
plaintext = pkcs7_unpad(
aes_cbc_decrypt(ciphertext, KEY, KEY)
).pack('C*')
raise InvalidFormat.new(plaintext) unless plaintext.ascii_only?
config = plaintext.split(';').map { |kv| kv.split('=') }.to_h
puts "Decoded data: #{config}"
puts "Admin detected: #{config['admin'] == 'true'}"
end
def exploit_server(input)
cookie = encode_cookie(input)
# Step 3 of the attack
16.times { |i| cookie[16 + i] = 0 }
# Step 4 of the attack
16.times { |i| cookie[32 + i] = cookie[i] }
begin
decode_cookie(cookie)
rescue InvalidFormat => e
puts "Invalid message!"
e.message
end
end
input = 'A' * (16 * 3)
# Step 5 of the attack
result = exploit_server(input)
blocks = result.bytes.each_slice(16).to_a
puts 'Original key:'
puts KEY.inspect
puts 'Leaked Key:'
# Step 6 of the attack
puts xor_bytes(blocks[0], blocks[2]).inspect
```

In order to keep the code short and to the point I’ve opted to hide methods that we have seem in previous posts, they are:

- pkcs7_pad
- pkcs7_unpad
- aes_cbc_encrypt
- aes_cbc_decrypt

See CBC Padding Oracle for more information about each method.

And we have reached the end of our exercise! This should be our last post on `CBC`

, on future posts we will start investigating a new block mode called `CTR`

. Please reach out to me on Twitter or by email if you have any questions or suggestions on how to improve this or future posts.