Search code examples
python-3.xencryptioncryptographyaespycrypto

AES encryption with CBC mode in python (128-Bit keys)


I encrypted a text in AES with the code below and decrypt the Ciphertext with Online decryption websites (Website 1, Website 2). But, the decrypted text from all websites contains some unwanted strings in front as shown in this picture.

import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES

key = b'asdfghjklqwertyu'

class AESCipher(object):

    def __init__(self, key): 
        self.bs = AES.block_size
        self.key = key

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw.encode()))

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

a = AESCipher(key)
print(a.encrypt('Hey There!'))

Solution

  • The devlan site explains

    CBC mode is highly recommended and it requires IV to make each message unique. If no IV is entered then default will be used here for CBC mode and that defaults to a zero based byte[16].

    So your prepended IV is considered as a ciphertext block. Now, you have two blocks there. In the CBC mode, the decryption is performed as ( blocks are counted form 1);

    Pi = Dec(key, Ci) + Ci
    C0 = IV
    
    • your P1 = Dec(key, C1) + C0 and this is garbage since the IV = 0
    • your P2 = Dec(key, C2) + C1 and this is your original message.

    This works due to the property of the CBC mode and the below diagram shows the case;

    enter image description here

    It is nice you tested there and there. You should read all documentation of a program before use, and this is more critical in Cryptographic applications.

    Your prepending the IV is correct and nothing was wrong there, you just need to supply your IV to the site and just the ciphertext blocks.

    If you need to decrypt the text without the IV, set the iv to default while encrypting.

    class AESCipher(object):
    
        def __init__(self, key): 
            self.bs = AES.block_size
            self.key = key
    
        def encrypt(self, raw):
            raw = self._pad(raw)
            iv = b'\0'*16 #Default zero based bytes[16]
            cipher = AES.new(self.key, AES.MODE_CBC, iv)
            return base64.b64encode(cipher.encrypt(raw.encode()))
    
        def _pad(self, s):
            return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)