156 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from functools import partial
 | ||
| import time
 | ||
| import numpy as np
 | ||
| import string
 | ||
| import matplotlib.pyplot as plt
 | ||
| from Crypto.PublicKey import RSA
 | ||
| from Crypto.Cipher import AES, PKCS1_OAEP
 | ||
| from Crypto.Util.Padding import pad, unpad
 | ||
| from Crypto.Random import get_random_bytes
 | ||
| from sympy import Matrix   # only for the 2×2 Hill inverse
 | ||
| 
 | ||
| 
 | ||
| # 1.  Hill-Cipher utilities  (2×2 matrix [[3,3],[2,5]])
 | ||
| 
 | ||
| hillkey = np.array([[3, 3],
 | ||
|                     [2, 5]], dtype=int)
 | ||
| ALPH = string.ascii_uppercase
 | ||
| m_len = 2                         # block size
 | ||
| 
 | ||
| def clean(txt):
 | ||
|     return ''.join(ch.upper() for ch in txt if ch.isalpha())
 | ||
| 
 | ||
| def hill_encrypt(pt, key=hillkey):
 | ||
|     pt = clean(pt)
 | ||
|     if len(pt) % m_len:                       # padding
 | ||
|         pt += 'X' * (m_len - len(pt) % m_len)
 | ||
|     cipher = []
 | ||
|     for i in range(0, len(pt), m_len):
 | ||
|         block = np.array([ALPH.index(ch) for ch in pt[i:i+m_len]])
 | ||
|         cipher.extend((key @ block) % 26)
 | ||
|     return ''.join(ALPH[i] for i in cipher)
 | ||
| 
 | ||
| def hill_decrypt(ct, key=hillkey):
 | ||
|     # modular inverse of the matrix
 | ||
|     M = Matrix(key)
 | ||
|     detinv = pow(int(M.det()) % 26, -1, 26)
 | ||
|     adj = M.adjugate()
 | ||
|     inv_key = (detinv * adj) % 26
 | ||
|     inv_key = np.array(inv_key).astype(int)
 | ||
| 
 | ||
|     plain = []
 | ||
|     for i in range(0, len(ct), m_len):
 | ||
|         block = np.array([ALPH.index(ch) for ch in ct[i:i+m_len]])
 | ||
|         plain.extend((inv_key @ block) % 26)
 | ||
|     return ''.join(ALPH[i] for i in plain)
 | ||
| 
 | ||
| # Generic timing wrapper
 | ||
| def timed(fn, *a, **kw):
 | ||
|     t0 = time.perf_counter()
 | ||
|     for _ in range(100_000):              # 100 000 loops → 1 000 000 calls
 | ||
|         fn(*a, **kw)
 | ||
|     delta = time.perf_counter() - t0
 | ||
|     return delta
 | ||
| 
 | ||
| # Menu
 | ||
| def menu():
 | ||
|     while True:
 | ||
|         print("\n===== Cryptographic Demo =====")
 | ||
|         print("1. Hill-cipher demo")
 | ||
|         print("2. RSA + envelope (share AES)")
 | ||
|         print("3. AES-128 encrypt / decrypt")
 | ||
|         print("4. Speed graph (100k it loops)")
 | ||
|         print("0. Quit")
 | ||
|         ch = input("Select >> ").strip()
 | ||
|         if ch == '0': break
 | ||
|         elif ch == '1': hill_demo()
 | ||
|         elif ch == '2': rsa_env_demo()
 | ||
|         elif ch == '3': aes_demo()
 | ||
|         elif ch == '4': speed_graph()
 | ||
|         else: print("Invalid choice")
 | ||
| 
 | ||
| # 1.  Hill demo
 | ||
| def hill_demo():
 | ||
|     msg = 'The  key is hidden under the mattress'
 | ||
|     print("\n------- 1. Hill-Cipher Demo -------")
 | ||
|     print("Key matrix:\n", hillkey)
 | ||
|     ct = hill_encrypt(msg)
 | ||
|     print("Cipher :", ct)
 | ||
|     pt = hill_decrypt(ct)
 | ||
|     print("Plain  :", pt)       # uppercase / padded – expected
 | ||
| 
 | ||
| # 2. RSA Envelope demo
 | ||
| AES_KEY = b"0123456789ABCDEF"    # 16-byte AES-128 key (truncated from prompt)
 | ||
| 
 | ||
| def rsa_env_demo():
 | ||
|     print("\n------- 2. RSA × AES envelope Demo -------")
 | ||
|     # generate RSA pairs
 | ||
|     encoder_priv = RSA.generate(2048)
 | ||
|     encoder_pub  = encoder_priv.publickey()
 | ||
| 
 | ||
|     decoder_priv = RSA.generate(2048)   # strictly not needed, but shown
 | ||
|     decoder_pub  = decoder_priv.publickey()
 | ||
| 
 | ||
|     # Use encoder_pub to encrypt the shared AES key
 | ||
|     cipher_rsa = PKCS1_OAEP.new(encoder_pub)
 | ||
|     enc_key = cipher_rsa.encrypt(AES_KEY)
 | ||
| 
 | ||
|     # Show keys
 | ||
|     print("Encoder public key (PEM):\n", encoder_pub.export_key().decode())
 | ||
|     print("Encoder private key (PEM):\n", encoder_priv.export_key().decode())
 | ||
|     print("Encrypted AES key (hex):", enc_key.hex())
 | ||
| 
 | ||
|     # Decrypt back using encoder’s private key
 | ||
|     dec_rsa = PKCS1_OAEP.new(encoder_priv)
 | ||
|     dec_key = dec_rsa.decrypt(enc_key)
 | ||
|     print("Decrypted AES key        :", dec_key.hex(), " match=", dec_key==AES_KEY)
 | ||
| 
 | ||
| # 3. AES-128
 | ||
| def aes_demo():
 | ||
|     msg = input("Enter AES plaintext:")
 | ||
|     print("\n------- 3. AES-128 Demo -------")
 | ||
|     cipher = AES.new(AES_KEY, AES.MODE_CBC)         # random IV
 | ||
|     ct = cipher.encrypt(pad(msg.encode(), AES.block_size))
 | ||
|     print("Raw key               :", AES_KEY.hex())
 | ||
|     print("Ciphertext (incl. IV) :", cipher.iv.hex() + ct.hex())
 | ||
|     decipher = AES.new(AES_KEY, AES.MODE_CBC, cipher.iv)
 | ||
|     pt = unpad(decipher.decrypt(ct), AES.block_size).decode()
 | ||
|     print("Recovered plaintext   :", pt)
 | ||
| 
 | ||
| # 4.  Speed test
 | ||
| def speed_graph():
 | ||
|     print("\n------- Benchmark (100 000 loops = 1 Mio calls) -------")
 | ||
|     # prepare one big dummy block for AES
 | ||
|     aes_cipher = AES.new(AES_KEY, AES.MODE_ECB)
 | ||
|     dummy_aes = b"A" * 16
 | ||
| 
 | ||
|     # RSA encryption of AES key just once
 | ||
|     pub_key = RSA.generate(2048).publickey()
 | ||
|     rsa_cipher = PKCS1_OAEP.new(pub_key)
 | ||
|     dummy_rsa = AES_KEY
 | ||
| 
 | ||
|     # Hill needs string
 | ||
|     hill_string = "HELLOWORLD"
 | ||
|     hill_ct = hill_encrypt(hill_string)   # pre-compute to keep test same
 | ||
| 
 | ||
|     # timing
 | ||
|     t_hill = timed(hill_encrypt, hill_string)
 | ||
|     t_rsa  = timed(rsa_cipher.encrypt, dummy_rsa)
 | ||
|     t_aes  = timed(aes_cipher.encrypt, dummy_aes)
 | ||
| 
 | ||
|     methods = ['Hill-cipher\n(str encrypt)', 'RSA-OAEP\n(key wrap)', 'AES-128\n(block)']
 | ||
|     timings = [t_hill, t_rsa, t_aes]
 | ||
|     for name,val in zip(methods,timings):
 | ||
|         print(f"{name:25s} : {val:9.4f} s (100 000 loops)")
 | ||
| 
 | ||
|     plt.bar(methods, timings, color=['gold','coral','dodgerblue'])
 | ||
|     plt.ylabel("Time (s) for 100 000 iterations")
 | ||
|     plt.title("Relative speed among 3 algorithms")
 | ||
|     plt.tight_layout()
 | ||
|     plt.show()
 | ||
| 
 | ||
| ##############################################################
 | ||
| # main
 | ||
| ##############################################################
 | ||
| if __name__ == "__main__":
 | ||
|     menu()
 |