[Crypto] Leaking IV in CBC mode

Sometimes, IV in CBC mode can be leaked, if a plaintext contains many zeroes or padding bytes.

Let's see.

Zero-padded buffer

This is a real case from my practice. I knew the AES key, but I had no idea that CBC is used and I tried to decrypt using ECB mode.

#!/usr/bin/env python3

import os, sys, binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key = b"some highly secret key 123456789"
IV=b"init vector here"

def encr(msg):
    cipher = Cipher(algorithms.AES(key), modes.CBC(IV))
    encryptor = cipher.encryptor()
    return encryptor.update(msg) + encryptor.finalize()

def decr_with_IV(msg):
    cipher = Cipher(algorithms.AES(key), modes.CBC(IV))
    decryptor = cipher.decryptor()
    return decryptor.update(msg) + decryptor.finalize()

def decr_without_IV(msg):
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    decryptor = cipher.decryptor()
    return decryptor.update(msg) + decryptor.finalize()

msg=b"Saturday"
padded_msg=msg+b"\0"*(16-len(msg))
encrypted=encr(padded_msg)
print (decr_with_IV(encrypted))
print (decr_without_IV(encrypted))


b'Saturday\x00\x00\x00\x00\x00\x00\x00\x00'
b':\x0f\x1d\x01R\x12\x04\x1ator here'

I saw only the ending of IV, but I quickly deduced its full value.

PKCS#7 padding

(See my another blog post about PKCS#7 padding.)

5-byte input file:

test

Encrypt with CBC mode:

# % echo "hello, world." | xxd -g 1
# 00000000: 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 2e 0a        hello, world..

openssl enc -aes-128-cbc -nosalt -e \
        -in input -out output \
        -K '112233445566778899aabbccddeeff00' -iv '68656c6c6f2c20776f726c642e2e2e2e'

Would be decrypted back:

openssl enc -aes-128-cbc -nosalt -d \
        -in output -out output2 \
        -K '112233445566778899aabbccddeeff00' -iv '68656c6c6f2c20776f726c642e2e2e2e'

But not in ECB mode, because of padding present:

openssl enc -aes-128-ecb -nosalt -d \
        -in output -out output2 \
        -K '112233445566778899aabbccddeeff00'
bad decrypt
40471EA6F7720000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:124:

A Python code to decrypt it, also, to cancel padding bytes:

#!/usr/bin/env python3

import os, sys, binascii
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key=b"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff\x00"
IV=b"\x68\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x2e\x2e\x2e\x2e"

def decr_with_IV(msg):
    cipher = Cipher(algorithms.AES(key), modes.CBC(IV))
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(msg) + decryptor.finalize()
    return plaintext

def decr_without_IV(msg):
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(msg) + decryptor.finalize()
    return plaintext

f=open("output", "rb")
x=decr_with_IV(f.read())
print (x)

f=open("output", "rb")
x=decr_without_IV(f.read())
print (list(map(lambda i: chr(i^0xb), x)))

In first case you see correct decryption and padding bytes. In second -- IV is leaked.

b'test\n\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
['\x17', '\x0b', '\x14', '\x13', 'n', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.', '.', '.', '.']

Without knowing padding length, it can be enumerated/bruteforced easily.

Summary

(Partial) IV can be recovered if some plaintext bytes are known.

(the post first published at 20250916.)


List of my other blog posts.

Subscribe to my news feed,

Some time ago (before 24-Mar-2025) there was Disqus JS script for comments. I dropped it --- it was so motley, distracting, animated, with too much ads. I never liked it. Also, comments didn"t appeared correctly (Disqus was buggy). Also, my blog is too chamberlike --- not many people write comments here. So I decided to switch to the model I once had at least in 2020 --- send me your comments by email (don"t forget to include URL to this blog post) and I"ll copy&paste it here manually.

Let"s party like it"s ~1993-1996, in this ultimate, radical and uncompromisingly primitive pre-web1.0-style blog and website.