3.2 UPDATED TECHNOLOGY

EXERCISE 3.2 UPDATED TECHNOLOGY

Upgrade the Caesar cipher application from Chapter 1 to use AES. Instead of specifying a shift value, figure out how to get keys in and out of the program. You will also have to deal with the 16-byte message size issue. Good luck!


The following program uses the PBKDF, scrypt with a fixed salt for generating the keys as input for the AES program. Thus the key can be any passphrase. Implementation is given in the kdf() function

The issue of padding has been solved by the two static functions inside of the class Padding.

# ex3_2.py

# Do NOT use ECB in production!!!
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import base64

def kdf(passphrase: str) -> bytes: 
    # In cryptography, a kdf (key derivation function) is a 
    # cryptographic algorithm that derives one or more secret keys
    # from a secret value such as a passphrase.
    # To read more: https://en.wikipedia.org/wiki/Key_derivation_function

    # a fixed, randomly generated salt, using os.urandom
    salt = b'x"&\xd9__7\xac\xc6\xcd\x06r\xd6\xe5\xce\x97'

    _scrypt = Scrypt(salt=salt, length=16, n=2**14, r=8, p=1, backend=default_backend())
    key = _scrypt.derive(passphrase.encode('utf-8'))
    return key

class Padding:
    @staticmethod    
    def pad_it_up(msg: bytes) -> bytes: 
        '''
        This function is used to pad the message.

        Technique: Determine the number of padding bytes required.
        This is a number n which satisfies 1 <= n <= 16
        and n + len(msg) is a multiple of 16. Pad the plaintext by appending
        n bytes, each with value n. (Read more in Chapter 4 of the book  
        "Cryptography Engineering by Niels Ferguson, Bruce Schneier, Tadayoshi Kohno".)
        ''' 
        n = 16 - len(msg) % 16
        return msg + bytes([n] * n)
    
    @staticmethod
    def unpad_it_up(msg: bytes) -> bytes: 
        '''this function is used to remove the pads from the message''' 
        n = msg[-1]
        # then ignore the last n bytes
        return msg[:-1*n]

def encrypt_using_aes_ecb(plaintext: str, key: bytes) -> str: 
    proper_plaintext = Padding.pad_it_up(plaintext.encode('utf-8'))
    aesCipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
    aesEncryptor = aesCipher.encryptor()
    ciphertext = aesEncryptor.update(proper_plaintext)
    return base64.b64encode(ciphertext).decode()


def decrypt_using_aes_ecb(ciphertext: str, key: bytes) -> str: 
    proper_ciphertext = base64.b64decode(ciphertext.encode())
    aesCipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
    aesDecryptor = aesCipher.decryptor()
    plaintext = aesDecryptor.update(proper_ciphertext)
    plaintext = Padding.unpad_it_up(plaintext)
    return plaintext.decode()

if __name__ == "__main__":
    passphrase = "very-bad-password"
    aes_key = kdf(passphrase=passphrase)
    while True:
        print("\nAES ECB MODE")
        print("--------------------")
        print("\tCurrent Passphrase: {}\n".format(passphrase))
        print("\t1. Encrypt Message.")
        print("\t2. Decrypt Message.")
        print("\t3. Change Passphrase.")
        print("\t4. Quit.\n")
        choice = input(">> ")
        print()

        if choice == '1':
            message = input("\nMessage to encrypt: ")
            print("Encrypted Message: {}".format(
                encrypt_using_aes_ecb(message, aes_key)))

        elif choice == '2':
            message = input("\nMessage to decrypt: ")
            print("Decrypted Message: {}".format(
                decrypt_using_aes_ecb(message, aes_key)))

        elif choice == '3':
            new_passphrase = input("\nNew passphrase (currently {}): ".format(passphrase))
            try:
                if len(new_passphrase) == 0:
                    raise Exception("Password cannot be empty")
            except ValueError:
                print("Password cannot be empty")
            else:
                passphrase = new_passphrase
                aes_key = kdf(passphrase)
        
        elif choice == '4': 
            exit()

        else:
            print("Unknown option {}.".format(choice))

The following is the output of the command pip freeze on Python 3.10.6:

cffi==1.15.1
cryptography==39.0.0
gmpy2==2.1.5
pycparser==2.21

The following video shows a session with the above program: