Three blocks of **plaintext** (green), each 16 bytes long,
are encrypted, producing three blocks of **ciphertext** (yellow).

The first block is XORed with an
**initialization vector** or **iv** (blue),
also 16 bytes long, and then encrypted
with the key.

Each subsequent block is XORed with the previous block of ciphertext.

- If one byte of padding is needed, use
`01`

- If two bytes of padding are needed, use
`0202`

- If three bytes of padding are needed, use
`030303`

- ...
- If fifteen bytes of padding are needed, use
`0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f`

- If no bytes of padding are needed, add an entire block of sixteen chr(16) characters, or
`10101010101010101010101010101010`

`plaintext[47] == bytes([1])`

That will garble the whole second block of plaintext:`ciphertext[31]`

But it will only change a single byte of the third block:`plaintext[16:32]`

This is shown below, with red indicating changed bytes.`plaintext[47]`

python3 -m pip install pycryptodome python3

from Crypto.Cipher import AES key = b"0000111122223333" iv = b"aaaabbbbccccdddd" cipher = AES.new(key, AES.MODE_CBC, iv) a = b"Hello from AES!!"

ciphertext = cipher.encrypt(a) print(ciphertext.hex())

cipher = AES.new(key, AES.MODE_CBC, iv) d = cipher.decrypt(ciphertext) print(d) print(d.hex())

from Crypto.Cipher import AES key = b"0000111122223333" iv = b"aaaabbbbccccdddd" cipher = AES.new(key, AES.MODE_CBC, iv) a = b"This simple sentence is forty-seven bytes long."

ciphertext = cipher.encrypt(a + bytes([1])) mod = ciphertext[0:31] + bytes([255]) + ciphertext[32:] print(ciphertext.hex()) print(mod.hex())

*Note: your values will be different,
but the location of the changed bytes
will be the same.*

cipher = AES.new(key, AES.MODE_CBC, iv) c = cipher.decrypt(ciphertext) cipher = AES.new(key, AES.MODE_CBC, iv) cmod = cipher.decrypt(mod) print(c.hex()) print(cmod.hex())

Use a text editor, such as nano or Notepad,
to make this file. Save it as **pador.py**
in your home directory.

from Crypto.Cipher import AES key = b"aaaabbbbccccdddd" iv = b"1111222233334444" def decr(ciphertext): cipher = AES.new(key, AES.MODE_CBC, iv) return ispkcs7(cipher.decrypt(ciphertext)) def ispkcs7(plaintext): l = len(plaintext) c = plaintext[l-1] if (c > 16) or (c < 1): return "PADDING ERROR" if plaintext[l-c:] != bytes([c])*c: return "PADDING ERROR" return plaintext def encr(plaintext): cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pkcs7(plaintext)) return ciphertext def pkcs7(plaintext): padbytes = 16 - len(plaintext) % 16 pad = padbytes * bytes([padbytes]) return plaintext + pad

python3

As shown below, the encrypted string is a long random series of hexadecimal values.

from pador import encr, decr a = b"This simple sentence is forty-seven bytes long." c = encr(a) print(c.hex())

The decrypted string ends in "01", a single byte of padding. It's not printable, so it can't be seen in the ASCII version, but it's visible in the hexadecimal version, as highlighted in the image below.

d = decr(c) print(d) print(d.hex())

Enter this command decrypt the original ciphertext, as shown below.

mod = c[0:47] + bytes([65]) decr(mod)

As shown below, an error message appears when the padding is incorrect, but not when it is correct.

decr(c)

This seemingly harmless error message is enough to completely defeat the encryption, because it can be used to leak out information about the encryption process.

You need to encrypt it with AES-CBC, but you don't know the IV or the key.

WIN

You have a single example of encrypted text, and a system that will let you test encrypted text and tell you whether the padding is correct or not.

This should be impossible, but it can be done using the padding oracle attack.

`intermediate[32:48]`

If we can figure out the intermediate state, we can create ciphertext that decrypts to any plaintext we want in the third block. We do that by modifying the second block.

We don't need to know the key or the iv.

1. You start with a valid ciphertext string.

2. Replace ciphertext[16:32] with any random
bytes. This will change the second and third
blocks of plaintext, including the last byte
of plaintext,
** plaintext[47]**,
which is colored red in
the figure below.

The padding of the plaintext will now be
invalid, unless ** plaintext[47]**
is 1. (There's a small chance that it might be valid
in other ways, but let's ignore that for now.)

3. Try all possible byte values for
** ciphertext[31]**
until a valid padding is found. Now we know that

`plaintext[47]`

4. The last intermediate byte is the XOR of those values:

`ciphertext[31] ^ 1`

After 256 guesses, we get one byte of the intermediate value. We can continue in this fashion until we get them all, except for the first block.

As shown below, the ciphertext is a string of 48 random bytes.

a = b"This sentence cleaarly says I'M A LOSER." original = encr(a) print(original.hex())

Now we are ready to perform the attack in four stages, as detailed below.

As shown below, all the modifications result in "PADDING ERROR" messages.

for i in range(5): mod = original[0:31] + bytes([i]) + original[32:] print(i, decr(mod))

As shown below, there are two valid values.

for i in range(256): mod = original[0:31] + bytes([i]) + original[32:] if decr(mod) != "PADDING ERROR": print(i, "is correctly padded")

One of these is the original byte, which results in a correct string of padding bytes, and the other one results in a final byte of 1, which is interpreted as a correct padding string one byte long.

Execute this command to see the original value of ciphertext[31].

As shown below, the original byte is 154. So the other value, 147, must result in a plaintext[47] value of 1.

print(original[31])

This was effective, but it would be better to find the value directly, instead of finding two possibilities and needing to choose between them.

As shown below, the correct value of 147 is found. This worked because the modified ciphertext creates random bytes of cleartext, which won't end in valid padding unless the final byte is 1.

prefix = original[0:16] + b"AAAAAAAAAAAAAAA" for i in range(256): mod = prefix + bytes([i]) + original[32:] if decr(mod) != "PADDING ERROR": print(i, "is correctly padded")

As shown below, the correct value of 147 is found. Almost any values can be used to fill ciphertext[16:31] and the attack will work.

prefix = original[0:16] + b"BBBBBBBBBBBBBBB" for i in range(256): mod = prefix + bytes([i]) + original[32:] if decr(mod) != "PADDING ERROR": print(i, "is correctly padded")

ciphertext[31] ^ intermediate[47] = plaintext[47]

Applying **
ciphertext[31] ^**
to both sides of this equation yields:

Rearranging terms yields:ciphertext[31] ^ ciphertext[31] ^ intermediate[47] = ciphertext[31] ^ plaintext[47]

On the left side,intermediate[47] ^ ciphertext[31] ^ ciphertext[31] = ciphertext[31] ^ plaintext[47]

And we know thatintermediate[47] = ciphertext[31] ^ plaintext[47]

So now we know this:intermediate[47] = ciphertext[31] ^ 1 = 147 ^ 1 = 146

intermediate[47] = 146

So we need to use this value of ciphertext[31]:intermediate[47] = 146

plaintext[47] = 2

ciphertext[31] = 146 ^ 2 = 144

As shown below, the answer is 6.

prefix = original[0:16] + b"BBBBBBBBBBBBBB" for i in range(256): mod = prefix + bytes([i]) + bytes([144]) + original[32:] if decr(mod) != "PADDING ERROR": print(i, "is correctly padded")

We can now calculate Intermediate[46]

So now we know this:intermediate[46] = ciphertext[30] ^ plaintext[46] = 6 ^ 2 = 4

intermediate[46] = 4

intermediate[47] = 146

So we need to use these values for ciphertext[30] and ciphertext[31]:intermediate[46] = 4

intermediate[47] = 146

plaintext[46] = 3

plaintext[47] = 3

ciphertext[30] = 4 ^ 3 = 7

ciphertext[31] = 146 ^ 3 = 145

Press **Enter** twice after the last line of text.

As shown below, the answer is 102.

prefix = original[0:16] + b"BBBBBBBBBBBBB" for i in range(256): mod = prefix + bytes([i]) + bytes([7]) + bytes([145]) + original[32:] if decr(mod) != "PADDING ERROR": print(i, "is correctly padded")

We can now calculate Intermediate[45]

So now we know this:intermediate[45] = ciphertext[29] ^ plaintext[45] = 102 ^ 3 = 101

intermediate[45] = 101

intermediate[46] = 4

intermediate[47] = 146

So we need to use these values for ciphertext[29], ciphertext[30], and ciphertext[31]:intermediate[45] = 101

intermediate[46] = 4

intermediate[47] = 146

plaintext[45] = 4

plaintext[46] = 4

plaintext[47] = 4

ciphertext[29] = 101 ^ 4 = 97

ciphertext[30] = 4 ^ 4 = 0

ciphertext[31] = 146 ^ 4 = 150

Press **Enter** twice after the last line of text.

As shown below, the answer is 235.

prefix = original[0:16] + b"BBBBBBBBBBBB" for i in range(256): mod = prefix + bytes([i]) + bytes([97]) + bytes([0]) + bytes([150]) + original[32:] if decr(mod) != "PADDING ERROR": print(i, "is correctly padded")

We can now calculate Intermediate[44]

So now we know this:intermediate[44] = ciphertext[28] ^ plaintext[44] = 235 ^ 4 = 239

intermediate[44] = 239

intermediate[45] = 101

intermediate[46] = 4

intermediate[47] = 146

We want this plaintext, ending with "WIN" and a correct single byte of 1 for padding:intermediate[44] = 239

intermediate[45] = 101

intermediate[46] = 4

intermediate[47] = 146

So we choose these ciphertext bytes:cleartext[44] = ord("W")

cleartext[45] = ord("I")

cleartext[46] = ord("N")

cleartext[47] = 1

Execute these commands to calculate and insert those bytes:ciphertext[28] = cleartext[44] ^ intermediate[44] = ord("W") ^ 239

ciphertext[29] = cleartext[45] ^ intermediate[45] = ord("I") ^ 101

ciphertext[30] = cleartext[46] ^ intermediate[46] = ord("N") ^ 4

ciphertext[31] = cleartext[47] ^ intermediate[47] = 1 ^ 146

c28 = ord("W") ^ 239 c29 = ord("I") ^ 101 c30 = ord("N") ^ 4 c31 = 1 ^ 146 ciphertext = original[0:28] + bytes([c28]) + bytes([c29]) \ + bytes([c30]) + bytes([c31]) + original[32:]

`decr(ciphertext)`

## C 501.1: Find Four Bytes (20 pts)

The flag is the four bytes covered by green rectangles in the image above, in hex, like this:AABBCCDD

This library takes as input a 96-character string, which encodes 48 bytes in hexadecimal. Those bytes contain ciphertext.

It then decrypts that string, expecting to find cleartext with a colon followed by a name. If it finds a name, it puts that name on the winners page.

If it fails, it provides overly informative error messages.

Your task is to construct ciphertext to fool the library into adding your name to the winners page.

Open a Terminal window and execute these commands:

In the Python shell, execute these commands:

cd Downloads mv padorchal2a.pyx padorchal2a.py python3

The first string should return

from padorchal2a import decr a = "3ceafc6720f418f86b937a14fa0703df352b04f7d1e6a1e3bdd2e6f36e3da543800a6b07b3db36b372e934dfeeb2d920" decr(a) c = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" decr(c)

This ciphertext is valid:

It decodes to this string:`3ceafc6720f418f86b937a14fa0703df352b04f7d1e6a1e3bdd2e6f36e3da543800a6b07b3db36b372e934dfeeb2d920`

It gives an error message because a name cannot begin with a space.`Put this name on the winners board: EXAMPLE`

To get on the winners board, send ciphertext in hex
that decodes to a string ending in ** :YOURNAME**
with correct padding. Don't use the literal string "YOURNAME"--use
a short version of your own name.

## C 501.2: Challenge: Winners Page (50 pts extra)

Use the form below to put your name on theWINNERS PAGE.When you get it, submit a text in the Canvas system for this project telling us the name you inserted on the winners board.

Block cipher mode of operation - Wikipedia

PKCS #7: Cryptographic Message Syntax (section 10.3)

Posted 10-19-17 5:26 am by Sam Bowne

Hex example added to form 11-18-17

Added to Crypto Hero 4-15-18

Ported to new scoring engine 7-8-19

Extra credit points specified 9-10-20

Updated to Python 3 9-13-20

C 501.2 updated to Python 3 11-25-21

Updated to use pador folder 9-25-23

Updated to use padorchal2a.pyx on 9-26-23

Updated to install pycryptodome 12-5-23