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
= b'x"&\xd9__7\xac\xc6\xcd\x06r\xd6\xe5\xce\x97'
salt
= Scrypt(salt=salt, length=16, n=2**14, r=8, p=1, backend=default_backend())
_scrypt = _scrypt.derive(passphrase.encode('utf-8'))
key 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".)
'''
= 16 - len(msg) % 16
n return msg + bytes([n] * n)
@staticmethod
def unpad_it_up(msg: bytes) -> bytes:
'''this function is used to remove the pads from the message'''
= msg[-1]
n # then ignore the last n bytes
return msg[:-1*n]
def encrypt_using_aes_ecb(plaintext: str, key: bytes) -> str:
= Padding.pad_it_up(plaintext.encode('utf-8'))
proper_plaintext = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
aesCipher = aesCipher.encryptor()
aesEncryptor = aesEncryptor.update(proper_plaintext)
ciphertext return base64.b64encode(ciphertext).decode()
def decrypt_using_aes_ecb(ciphertext: str, key: bytes) -> str:
= base64.b64decode(ciphertext.encode())
proper_ciphertext = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
aesCipher = aesCipher.decryptor()
aesDecryptor = aesDecryptor.update(proper_ciphertext)
plaintext = Padding.unpad_it_up(plaintext)
plaintext return plaintext.decode()
if __name__ == "__main__":
= "very-bad-password"
passphrase = kdf(passphrase=passphrase)
aes_key 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")
= input(">> ")
choice print()
if choice == '1':
= input("\nMessage to encrypt: ")
message print("Encrypted Message: {}".format(
encrypt_using_aes_ecb(message, aes_key)))
elif choice == '2':
= input("\nMessage to decrypt: ")
message print("Decrypted Message: {}".format(
decrypt_using_aes_ecb(message, aes_key)))
elif choice == '3':
= input("\nNew passphrase (currently {}): ".format(passphrase))
new_passphrase try:
if len(new_passphrase) == 0:
raise Exception("Password cannot be empty")
except ValueError:
print("Password cannot be empty")
else:
= new_passphrase
passphrase = kdf(passphrase)
aes_key
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: