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] == chr(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]`

python

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

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

print ciphertext.encode("hex") print mod.encode("hex")

print cipher.decrypt(ciphertext).encode("hex") print cipher.decrypt(mod).encode("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 = "aaaabbbbccccdddd" iv = "1111222233334444" def decr(ciphertext): cipher = AES.new(key, AES.MODE_CBC, iv) return ispkcs7(cipher.decrypt(ciphertext)) def ispkcs7(plaintext): l = len(plaintext) c = ord(plaintext[l-1]) if (c > 16) or (c < 1): return "PADDING ERROR" if plaintext[l-c:] != chr(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 * chr(padbytes) return plaintext + pad

python

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

from pador import encr, decr a = "This simple sentence is forty-seven bytes long." c = encr(a) print c.encode("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.encode("hex")

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

mod = c[0:47] + chr(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 = "This sentence cleaarly says I'M A LOSER." original = encr(a) print original.encode("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] + chr(i) + original[32:] print i, decr(mod)

As shown below, there are two valid values.

for i in range(256): mod = original[0:31] + chr(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 ord(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] + "AAAAAAAAAAAAAAA" for i in range(256): mod = prefix + chr(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] + "BBBBBBBBBBBBBBB" for i in range(256): mod = prefix + chr(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] + "BBBBBBBBBBBBBB" for i in range(256): mod = prefix + chr(i) + chr(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] + "BBBBBBBBBBBBB" for i in range(256): mod = prefix + chr(i) + chr(7) + chr(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] + "BBBBBBBBBBBB" for i in range(256): mod = prefix + chr(i) + chr(97) + chr(0) + chr(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 (it's a little clumsy because Python doesn't let you assign a byte inside a string):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] + chr(c28) + chr(c29) + chr(c30) + chr(c31) + original[32:]`

`decr(ciphertext)`

Capture a full-screen image.

**YOU MUST SUBMIT A FULL-SCREEN IMAGE FOR FULL CREDIT!**

Save the image with the filename "**YOUR NAME Proj X4a**", replacing "YOUR NAME" with your real name.

Open a Terminal window and execute these commands:

In the Python shell, execute these commands:

cd Downloads mv padorchal1.pyx padorchal1.py python

The first string should return

from padorchal1 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.

Use the form
below to put your name on the
**WINNERS PAGE**.

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 CNIT 123 11-29-17