# Information Security HW 1 Report 0856622 余家宏 + [Github Link](https://github.com/yamiefun/Information-Security-HW1) + It's recommended to read this report on hackmd for a better reading experience. Links are provided in Github above. ## 1. Generate Random File with Specific File Size ```python= def create_plaintext(mbytes): ret = os.urandom(mbytes*1024*1024+1) return ret ``` I used this function to create random files as plaintext. The input of this function is the size of the random file in MBytes. Since the input of `os.urandom` is the bytes of target data, I multiplied `mbytes` by $1024^2$ to convert the unit. Additionally, I added $1$ extra byte to the plaintext to make it need to be padded while doing encryption. + [urandom DOC](https://docs.python.org/3/library/os.html#os.urandom) ## 2. Cryptography Library + [PyCryptodome](https://pycryptodome.readthedocs.io/en/latest/src/introduction.html) In this project, all encryption and decryption functions are supported by `PyCryptodome(v3.9.9)`. You should checkout the link above to install `PyCryptodome` to your environment if you want to reproduce my works. ## 3. Padding + [pad](https://pycryptodome.readthedocs.io/en/latest/src/util/util.html?highlight=pad#Crypto.Util.Padding.pad) The padding function I used in this project is supported by `PyCryptodome`. The default padding style is `PKCS7`. Please checkout the link above for more details. ## 4. Implementation of Different Encryption Methods In the code `task4.py`, there's a function called `encrypt`, which contains all the encryption methods used in this project. The input of `encrypt` function is a byte stream plaintext and a encryption methods. In the main function, I used a `for` loop to iterate all kinds of encryption methods, running them with different length of plaintext, and compared the running time with a chart. ### 4.1 AES with Different Mode of Operations #### Reference + [AES-CBC](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html?highlight=cbc#cbc-mode) + [AES-OCB](https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html?highlight=ocb#ocb-mode) + [AES-GCM](https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html?highlight=gcm#gcm-mode) + [AES-CCM](https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html?highlight=ccm#ccm-mode) #### Implementation ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad key = get_random_bytes(32) if mode == "AES_CBC": iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv) start_time = time.time() ret = cipher.encrypt(pad(plaintext, AES.block_size)) elif mode == "AES_OCB": cipher = AES.new(key, AES.MODE_OCB) start_time = time.time() ret, _ = cipher.encrypt_and_digest(pad(plaintext, AES.block_size)) elif mode == "AES_GCM": cipher = AES.new(key, AES.MODE_GCM) start_time = time.time() ret, _ = cipher.encrypt_and_digest(pad(plaintext, AES.block_size)) elif mode == "AES_CCM": cipher = AES.new(key, AES.MODE_CCM) start_time = time.time() ret, _ = cipher.encrypt_and_digest(pad(plaintext, AES.block_size)) ``` + Line4: The key length should be 32 bytes for AES-256. + Line6: IV is generated by `urandom` for CBC mode. + The running time only includes the `encrypt` function of each mode. Creating the `cipher` object is not included. ### RSA #### Reference + [RSA](https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html?highlight=RSA#rsa) #### Implementation ```python= from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA elif mode == "RSA1024": # generate private key key = RSA.generate(1024) encrypted_key = key.export_key() file_out = open("rsa_key.bin", "wb") file_out.write(encrypted_key) file_out.close() encoded_key = open("rsa_key.bin", "rb").read() key = RSA.import_key(encoded_key) cipher = PKCS1_OAEP.new(key) # max block size of RSA 1024 is 86 Bytes blk_size = 86 start_time = time.time() for i in range(0, len(plaintext), blk_size): blk_plain = plaintext[i:i+blk_size] # ret = cipher.encrypt(blk_plain) ``` + I'm not showing both RSA1024 and RSA2048 codes because the only difference is the length of the key and the maxium block size. + Since the length of plaintext is limited by the mechanism of RSA, the recommanded method using RSA is to encrypt an AES key with RSA, and encrypt message with AES. + In this project, I splitted the long plaintext into small blocks, and encrypt them separately by RSA. + After testing, the maximun length of plaintext is 86 for RSA1024, 214 for RSA2048. ### Hash #### Reference + [Crypto-hash](https://pycryptodome.readthedocs.io/en/latest/src/hash/sha256.html?highlight=sha256#sha-256) + [Hashlib-hash](https://docs.python.org/3/library/hashlib.html#module-hashlib) #### Implementation ```python= from Crypto.Hash import SHA256 elif mode == "SHA": hash_obj = SHA256.new() start_time = time.time() hash_obj.update(plaintext) ret = hash_obj.hexdigest() ``` After testing, I found that `SHA256` provided in `Crypto` is surprisingly slow. So I tried to use another `SHA256` function in `hashlib`, and the speed was as fast as expected. ```python= import hashlib elif mode == "SHA": m = hashlib.sha256() start_time = time.time() m.update(plaintext) ret = m.digest() ``` ### ChaCha20 #### Reference + [ChaCha20](https://pycryptodome.readthedocs.io/en/latest/src/cipher/chacha20.html?highlight=chacha20#chacha20-and-xchacha20) #### Implementation ```python= from Crypto.Cipher import ChaCha20 elif mode == "CC20": key = get_random_bytes(32) cipher = ChaCha20.new(key=key) start_time = time.time() ret = cipher.encrypt(plaintext) ``` + ChaCha20 is a stream cipher designed by Daniel J. Bernstein. + The length of the key of ChaCha20 is 32 bytes. ### ChaCha20-Poly1305 #### Reference + [ChaCha20-Poly1305](https://pycryptodome.readthedocs.io/en/latest/src/cipher/chacha20_poly1305.html#chacha20-poly1305-and-xchacha20-poly1305) #### Implementation ```python= from Crypto.Cipher import ChaCha20_Poly1305 elif mode == "CCP": key = get_random_bytes(32) cipher = ChaCha20_Poly1305.new(key=key) start_time = time.time() ret, _ = cipher.encrypt_and_digest(plaintext) ``` + ChaCha20-Poly1305 is an authenticated cipher with associated data (AEAD). + The length of the key is 32 bytes, and must never be reused. ### MAC #### Reference + [Poly1305](https://pycryptodome.readthedocs.io/en/latest/src/hash/poly1305.html?highlight=poly1305#poly1305) + [HMAC](https://pycryptodome.readthedocs.io/en/latest/src/hash/hmac.html?highlight=HMAC#hmac) + [CBC-MAC](https://pycryptodome.readthedocs.io/en/latest/src/hash/cmac.html?highlight=CMAC#cmac) #### Implementation ```python= from Crypto.Hash import Poly1305 from Crypto.Hash import HMAC from Crypto.Hash import CMAC elif mode == "POLY": key = get_random_bytes(32) mac = Poly1305.new(key=key, cipher=AES) start_time = time.time() mac.update(plaintext) ret = mac.hexdigest() elif mode == "HMAC": h = HMAC.new(key, digestmod=SHA256) start_time = time.time() h.update(plaintext) ret = h.hexdigest() elif mode == "CMAC": cobj = CMAC.new(key, ciphermod=AES) start_time = time.time() cobj.update(plaintext) ret = cobj.hexdigest() ``` + The length of the key of Poly1305 is 32 bytes. + The cipher of Poly1305 could be ChaCha20. ## Result ![](https://i.imgur.com/w7KMT9l.jpg) + Each line is average of 100 times testing. + RSA is not shown in this image because it's too slow to compare with other methods. ## 5. Why Key as IV of AES-CBC is Bad The code below follows the steps in cource slides. If user takes key as IV in AES-CBC, man in the middle can guess the key by using chosen ciphertext attack. ### Step 0 Define key and IV as the same random value. ```python= key = get_random_bytes(16) iv = key ``` ### Step 1 Create plaintext $P$. The length of $P$ is 3 AES blocks. ```python= P = get_random_bytes(16*3) ``` ### Step 2 Encrypt plaintext $P$ and get ciphertext $C$. ```python= e_cipher = AES.new(key, AES.MODE_CBC, iv) C = e_cipher.encrypt(P) ``` ### Step 3 Man in middle receive $C$, modify the middle of $C$ to bytes with all zeros, call $C'$. ```python= z = bytearray(16) C_pr = C[0:16]+z+C[0:16] ``` ### Step 4 Decrypt $C'$ by chosen ciphertext attack, get $P'$, and separate it to 3 parts, $P_1'$, $P_2'$, and $P_3'$. ```python= d_cipher = AES.new(key, AES.MODE_CBC, iv) P_pr = d_cipher.decrypt(C_pr) P1_pr = P_pr[0:16] P2_pr = P_pr[16:32] P3_pr = P_pr[32:48] ``` ### Step 5 Man in middle now can guess key by XORing $P_1'$ and $P_3'$. ```python= guess_key = bytes(a ^ b for a, b in zip(P3_pr, P1_pr)) ``` ### Step 6 Verify that guessed key is the same as the real key. ```python= print("Original key: {}".format(base64.b64encode(key).decode('utf-8'))) print("Guessed key: {}".format(base64.b64encode(guess_key).decode( 'utf-8'))) if guess_key == key: print("Guessed key is correct.") else: print("Guessed key is not correct.") ``` ### Result ![](https://i.imgur.com/vYlxoO7.png)