3.12 HAND-CRAFTED CBC
EXERCISE 3.12: HAND-CRAFTED CBC
ECB mode is just raw AES. You can create your own CBC Mode using ECB as the building block (Never use this for production code! Always use well-tested libraries). For this exercise, see if you can build a CBC encryption and decryption operation that is compatible with the
cryptography
library. For encryption, remember to take the output of each block and XOR it with the plaintext of the next block before encryption. Reverse the process for decryption.
# ex3_12.py
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
class MyOwnCBC:
def __init__(self, key: bytes, iv: bytes):
self.key = key
self.iv = iv
def encryptor(self):
return Encryptor(config=self)
def decryptor(self):
return Decryptor(config=self)
class Encryptor:
def __init__(self, config: MyOwnCBC):
self.config = config
self.buffer = b""
self.previous_ciphertext = self.config.iv
self._encryptor = Cipher(
=algorithms.AES(self.config.key),
algorithm=modes.ECB(),
mode=default_backend(),
backend
).encryptor()
def update(self, plaintext: bytes) -> bytes:
self.buffer += plaintext
= b""
retval while len(self.buffer) >= 16:
= self.buffer[:16]
single_block self.buffer = self.buffer[16:]
= xor_two_byte_strings(single_block, self.previous_ciphertext)
pre_cipher_block self.previous_ciphertext = self._encryptor.update(pre_cipher_block)
+= self.previous_ciphertext
retval return retval
def finalize(self):
assert(len(self.buffer) == 0)
return b""
class Decryptor:
def __init__(self, config: MyOwnCBC):
self.config = config
self.buffer = b""
self.previous_ciphertext = self.config.iv
self._decryptor = Cipher(
=algorithms.AES(self.config.key),
algorithm=modes.ECB(),
mode=default_backend(),
backend
).decryptor()
def update(self, ciphertext: bytes) -> bytes:
self.buffer += ciphertext
= b""
retval while len(self.buffer) >= 16:
= self.buffer[:16]
single_block self.buffer = self.buffer[16:]
= self._decryptor.update(single_block)
pre_plaintext_block += xor_two_byte_strings(pre_plaintext_block, self.previous_ciphertext)
retval self.previous_ciphertext = single_block
return retval
def finalize(self):
assert(len(self.buffer) == 0)
return b""
# the following function is taken from Exercise 3.9.
def xor_two_byte_strings(x: bytes, y: bytes) -> bytes:
assert(len(x) == len(y))
= []
result for _1, _2 in zip(x, y):
^ _2)
result.append(_1 return bytes(result)
if __name__ == '__main__':
= os.urandom(32)
key = os.urandom(16)
iv
= b"This is a very secret and long plaintext..."
plaintext # pad it up, so that the length of plaintext is a multiple of 16 bytes.
= plaintext + b"\x00"*(16 - (len(plaintext)%16))
plaintext
= MyOwnCBC(key=key, iv=iv)
my_cbc = my_cbc.encryptor()
my_cbc_encryptor = my_cbc.decryptor()
my_cbc_decryptor
= my_cbc_encryptor.update(plaintext)
ciphertext1 print(my_cbc_decryptor.update(ciphertext=ciphertext1))
= Cipher(
official_cbc =algorithms.AES(key),
algorithm=modes.CBC(iv),
mode=default_backend(),
backend
)= official_cbc.encryptor()
official_cbc_encryptor = official_cbc.decryptor()
official_cbc_decryptor
= official_cbc_encryptor.update(plaintext)
ciphertext2 print(official_cbc_decryptor.update(ciphertext2))
print(f"Passed: {ciphertext1 == ciphertext2}")
So, that is how you build your own AES-CBC!!
Running the above code, gives the following result:
Also, note that the plaintext is 3 blocks long.