├── .gitignore ├── app ├── utils │ ├── __init__.py │ └── utils.py ├── client │ ├── __init__.py │ └── client.py ├── server │ ├── __init__.py │ └── server.py └── keys │ ├── AES │ └── key.pem │ └── RSA │ ├── public.pem │ └── private.pem ├── requirements.txt ├── test_client.py ├── README.md ├── test_server.py └── run.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /bc_env -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import * -------------------------------------------------------------------------------- /app/client/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client -------------------------------------------------------------------------------- /app/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .server import Server -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cffi==1.15.1 2 | pycparser==2.21 3 | pycryptodome==3.17 4 | -------------------------------------------------------------------------------- /app/keys/AES/key.pem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samiptimalsena/Secure-Data-Transmission/master/app/keys/AES/key.pem -------------------------------------------------------------------------------- /app/keys/RSA/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunnoLhaC1jkVlcj7nmpi 3 | Xi5UceXZWnxoF80I6EyansWxx4DWjywgB2i5KrkRP3p3yFBSRB5niPz+Tr0d8Kc0 4 | 8nT91jC8hHr/y7jf4QNZNXDOxL10ZDH9NGdDVt8JpG8uOy5SMHUSHHtlOJfBqxKu 5 | CzSr1V+RT3gBwOUIJJxpzbYEuTJ15EmFfM7ywgOd+OTjA9sJUGPSz3cpkOg3olk/ 6 | JUJ5FG6ykIULkskt4nd8QOPSZ1Q6RGazlqiQWGp+XdV/Ora/JemT/r6yq/e/7G37 7 | ievOocs0NDPFPsy5bpZrDTgtN1K6gcc8Cd2Hj3eRm0qDoFT/sytaDmTjrTFAGbW1 8 | ZwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /test_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from app.client import Client 4 | from app.utils import decrypt_AES, encrypt_AES 5 | 6 | class TestClient(unittest.TestCase): 7 | def setUp(self): 8 | self.client = Client() 9 | 10 | def test_gen_AES_key(self): 11 | # Test if the generated AES key is of correct length 12 | key = Client.gen_AES_key(2) 13 | self.assertEqual(len(key), 32) 14 | 15 | def test_key_property(self): 16 | # Test if the key property returns the correct AES key 17 | self.assertEqual(self.client.key, self.client._AES_key) 18 | 19 | def test_encrypt_msg(self): 20 | # Test if the encryption of message using AES key is correct 21 | nonce, tag, ciphertext = self.client.encrypt_msg(b"Test message") 22 | self.assertEqual(decrypt_AES(self.client.key, nonce, tag, ciphertext), b"Test message") 23 | 24 | 25 | if __name__ == "__main__": 26 | unittest.main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secure-Data-Transmission 2 | COMP-492 assignment that demonstrates the use of AES and RSA algorithms for secure data sharing between client and server. 3 | 4 | **AES** is used to encrypt the actual data and **RSA** is used to encrypt the AES key that is securely transferred between client and server along with the encrypted data. 5 | The encryption of AES key is done using the public key of the server so that later the server is able to decrypt it using its own private key. 6 | 7 | ## Usage 8 | ``` 9 | $ python3 -m venv venv && source venv/bin/activate && pip3 install -r requirements.txt 10 | $ python run.py -m $MESSAGE 11 | ``` 12 | 13 | **Note**
14 | The message passed and the response to get for successfull secure transmission is as below: 15 | ```python 16 | { 17 | "Who are you?": "I am secure data transmission service.", 18 | "What algorithms do you use?": "I use AES and RSA for encryption and decryption" 19 | } 20 | ``` 21 | 22 | For all other message passed, we get a same response as `Sorry, unable to answer this question.` 23 | -------------------------------------------------------------------------------- /test_server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from Crypto.PublicKey import RSA 5 | 6 | from app.utils import encrypt_RSA, decrypt_RSA, load_RSA_key, save_RSA_key 7 | from app.server import Server 8 | 9 | 10 | class TestServer(unittest.TestCase): 11 | def setUp(self): 12 | self.server = Server() 13 | 14 | def tearDown(self): 15 | # Remove RSA keys from disk after each test 16 | os.remove("app/keys/RSA/private.pem") 17 | os.remove("app/keys/RSA/public.pem") 18 | 19 | def test_gen_RSA_key(self): 20 | pr_key, pb_key = self.server.gen_RSA_key() 21 | save_RSA_key(pr_key, "app/keys/RSA/private.pem") 22 | save_RSA_key(pb_key, "app/keys/RSA/public.pem") 23 | pr_key = load_RSA_key("app/keys/RSA/private.pem") 24 | pb_key = load_RSA_key("app/keys/RSA/public.pem") 25 | 26 | self.assertIsInstance(pr_key, RSA.RsaKey) 27 | self.assertIsInstance(pb_key, RSA.RsaKey) 28 | 29 | 30 | def test_public_key(self): 31 | pb_key = self.server.public_key 32 | self.assertIsInstance(pb_key, RSA.RsaKey) 33 | 34 | def test_get_AES(self): 35 | # Generate an AES key and encrypt it with the server public key 36 | AES_key = os.urandom(16) 37 | enc_AES = encrypt_RSA(self.server.public_key, AES_key) 38 | # Decrypt the AES key using the server private key 39 | decrypted_AES = self.server.get_AES(enc_AES) 40 | self.assertEqual(AES_key, decrypted_AES) 41 | 42 | if __name__ == '__main__': 43 | unittest.main() -------------------------------------------------------------------------------- /app/utils/utils.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from Crypto.Util.Padding import pad, unpad 3 | from Crypto.PublicKey import RSA 4 | from Crypto.Cipher import PKCS1_OAEP 5 | 6 | 7 | def encrypt_AES(key, plaintext): 8 | cipher = AES.new(key, AES.MODE_EAX) 9 | ciphertext, tag = cipher.encrypt_and_digest(pad(plaintext, AES.block_size)) 10 | return (cipher.nonce, tag, ciphertext) 11 | 12 | def decrypt_AES(key, nonce, tag, ciphertext): 13 | cipher = AES.new(key, AES.MODE_EAX, nonce) 14 | plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) 15 | try: 16 | cipher.verify(tag) 17 | return plaintext 18 | except ValueError: 19 | return None 20 | 21 | def generate_RSA_key(n=2048): 22 | private_key = RSA.generate(n) 23 | public_key = private_key.publickey() 24 | # return bytes(str(private_key), encoding="utf-8"), bytes(str(public_key), encoding="utf-8") 25 | return private_key.export_key(), public_key.export_key() 26 | 27 | def encrypt_RSA(public_key, data): 28 | cipher = PKCS1_OAEP.new(public_key) 29 | return cipher.encrypt(data) 30 | 31 | def decrypt_RSA(private_key, encrypted_data): 32 | cipher = PKCS1_OAEP.new(private_key) 33 | return cipher.decrypt(encrypted_data) 34 | 35 | def load_RSA_key(path): 36 | with open(path) as f: 37 | key = RSA.import_key(f.read()) 38 | return key 39 | 40 | def save_RSA_key(key, path): 41 | with open(path, "wb") as f: 42 | f.write(key) 43 | 44 | def load_AES_key(path): 45 | with open(path, "rb") as f: 46 | key = f.read() 47 | return key 48 | 49 | def save_AES_key(key, path): 50 | with open(path, "wb") as f: 51 | f.write(key) 52 | -------------------------------------------------------------------------------- /app/keys/RSA/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAunnoLhaC1jkVlcj7nmpiXi5UceXZWnxoF80I6EyansWxx4DW 3 | jywgB2i5KrkRP3p3yFBSRB5niPz+Tr0d8Kc08nT91jC8hHr/y7jf4QNZNXDOxL10 4 | ZDH9NGdDVt8JpG8uOy5SMHUSHHtlOJfBqxKuCzSr1V+RT3gBwOUIJJxpzbYEuTJ1 5 | 5EmFfM7ywgOd+OTjA9sJUGPSz3cpkOg3olk/JUJ5FG6ykIULkskt4nd8QOPSZ1Q6 6 | RGazlqiQWGp+XdV/Ora/JemT/r6yq/e/7G37ievOocs0NDPFPsy5bpZrDTgtN1K6 7 | gcc8Cd2Hj3eRm0qDoFT/sytaDmTjrTFAGbW1ZwIDAQABAoIBAEE21AeWMNCJay6U 8 | eMbIjrlAO6EPkKlv+5ljR7ux2psvKULLoH22oUbmDWfPDCSmiFCCIpsXBP9n9uoD 9 | gn6Ag2hNmN448hzkxSNydr1DBgZF+tHcLeHCh7o1aNvvKXibt0/UHcM7lgdm8hnO 10 | HNlesYZSt8tp3jAsGHTkxCnFl8zsQurCJJ42lVJH1mYsuAzG6DSM5aE2qzVsevTh 11 | kQfpnzVWF3RvFeqFJat2AXzRdilAPcz6TS8VkFeKDBsG0cHknk/IKOxTsOH7Wehi 12 | DdXC4dQpuVNfJcyw56vo/0qm/iFgsgudf0bn4xoxLtZOUUG7KEKl8TqUeVCyEVfN 13 | yaZh53UCgYEAxPpHjs/w1qPVQxT6/WDItrJmXozlVsOh6Iar/Eacp3TcQk9cJdBX 14 | CA0aBJIUkoEly+GCLOUhBAWzyW+W7ZtngfElcFCm6Z7avuxlvMkkkXrA5zmGrE10 15 | 83me3Bkv1jfZia+o+/kE6HnSZ8Mn3jeC1B7syWuWZ8TWBK/8nSIZ1k0CgYEA8loV 16 | gS51YfmMq2z/WqaQW9bkL+uBbbhjkmrI0AIcVhz8b21NRniJpX2F3kMoGaQ9CJp4 17 | ygVr+F45s9kM4PRO/d6KoFN5jNzdGdKXrajVMy7PbmPVbPfD9XyjA5CyUZxyNWrR 18 | OWORfNECAQPWwxDO4mms1senqvCpP1mcEpDPPIMCgYEAl2jq6FPehJ6ap13RMELd 19 | BdJL7hHPl8n5AovCV8DPWcjyJeZqpoZm38FyZmEDfznWm8lUDr5xim33rLciBLKX 20 | 9cIvTQ4xvxJkgq2j5mGWwlzhOtt1May4YQ7zlaumobt8c+Gau1TG+QvrN+dDFgUF 21 | g5fh21Hf4xPijufX/dDgm2ECgYEApnOg2XzjOkt/hi3ypHxKIgnR5+fEmWtPWDzn 22 | d5Y7iBmYd5+Gt6QKRhOUYjeVzyaylLojw/09TvTIhPHKmqaxqkOyyrMKhOkdIqM1 23 | chzHccPwzEzdRV5WxMC55bT4BH2DI5Ud5mCwiB21VqUgWBGvRmp2P34ZdpqcY9pU 24 | C5xR+akCgYEAnUTWQ1dGmzshN/ff4PtpTRNgcq0E8t9TaKxIacH5bWV1llgjXMl3 25 | 6tobfJ2nXudFPZ1Ph8/m3hECtEpVoxVUbioYvgQuHj8RC2Pb+buv06wSyozzvEcu 26 | LupLPN+QE9kUpv0zzRAnZ43fTXBksVt6FbyOz+O8U8RDomFVFeldUvs= 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from app.client import Client 3 | from app.server import Server 4 | 5 | def main(): 6 | parser = argparse.ArgumentParser(description='Safe Data Transmission Service') 7 | parser.add_argument('-m','--message', help='Message to pass to the server', required=True) 8 | args = vars(parser.parse_args()) 9 | 10 | # Getting message from argument parser and converting it to bytes format 11 | msg = bytes(args.get("message"), encoding="utf-8") 12 | 13 | # Initializing mock client and server 14 | client = Client() 15 | server = Server() 16 | 17 | # Encypting message to pass 18 | nonce, tag, ciphertext = client.encrypt_msg(msg) 19 | 20 | # Encrypting AES Key of Client using public key of the server. 21 | enc_aes_key = client.encrypt_key(server.public_key) 22 | 23 | # Now the message encrypted with AES key and the encryped AES key with public key of server is passed to the server for reply 24 | # The reply is as follow: 25 | # "Who are you?": "I am secure data transmission service.", 26 | # "What algorithms do you use?": "I use AES and RSA for encryption and decryption",x 27 | # For other question asked the server replies: "Sorry, unable to answer this question." 28 | server_response = client.call_server(server, ciphertext, enc_aes_key, nonce, tag) 29 | print("\nResponse: ") 30 | print(server_response) 31 | print() 32 | 33 | if __name__ == "__main__": 34 | main() 35 | 36 | """ 37 | Example: 38 | python run.py -m "Who are you?" 39 | -> b'Who are you? ==> I am secure data transmission service.' 40 | 41 | python run.py -m "What algorithms do you use?" 42 | -> b'What algorithms do you use? ==> I use AES and RSA for encryption and decryption' 43 | 44 | python run.py -m "Who am I?" 45 | -> b'Who am I? ==> Sorry, unable to answer this question.' 46 | """ 47 | -------------------------------------------------------------------------------- /app/client/client.py: -------------------------------------------------------------------------------- 1 | from Crypto.Random import get_random_bytes 2 | from Crypto.Cipher import AES 3 | from glob import glob 4 | 5 | from app.utils import encrypt_AES, decrypt_AES,save_AES_key, load_AES_key, encrypt_RSA 6 | 7 | class Client: 8 | """ 9 | A Mock client 10 | """ 11 | def __init__(self, key_size:int = 2): 12 | if not glob("app/keys/AES/*.pem"): 13 | # If the key is not already present inside keys direction, it is generated here. 14 | print("New aes key generated") 15 | self._AES_key = self.gen_AES_key(key_size) 16 | # cipher = AES.new(self._AES_key, AES.MODE_EAX) 17 | save_AES_key(self._AES_key, "app/keys/AES/key.pem") 18 | else: 19 | # Loading the saved key 20 | self._AES_key = load_AES_key("app/keys/AES/key.pem") 21 | 22 | @classmethod 23 | def gen_AES_key(cls, key_size): 24 | key = get_random_bytes(AES.key_size[key_size]) 25 | return key 26 | 27 | @property 28 | def key(self): 29 | return self._AES_key 30 | 31 | def encrypt_msg(self, msg:bytes) -> None: 32 | """ 33 | Encryping the messaage using AES key 34 | 35 | Args: 36 | msg: Message to encrypt 37 | Returns: 38 | None 39 | """ 40 | nonce, tag, ciphertext = encrypt_AES(self.key, msg) 41 | return nonce, tag, ciphertext 42 | 43 | def encrypt_key(self, pb_key:str) -> str: 44 | """ 45 | Encrypting the AES key using the server's public key 46 | 47 | Args: 48 | pb_key: Public key of the server 49 | 50 | Returns: 51 | key_enc: Encoded AES key 52 | """ 53 | key_enc = encrypt_RSA(pb_key, self.key) 54 | return key_enc 55 | 56 | def call_server(self, server, msg, enc_aes_key, nonce, tag): 57 | """ 58 | Calling the server for the response 59 | """ 60 | nonce_r, tag_r, ciphertext_r = server.reply(msg, enc_aes_key, nonce, tag) 61 | reply_msg = decrypt_AES(self._AES_key, nonce_r, tag_r, ciphertext_r) 62 | return reply_msg 63 | -------------------------------------------------------------------------------- /app/server/server.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from app.utils import generate_RSA_key, encrypt_RSA, decrypt_RSA, load_RSA_key, save_RSA_key, decrypt_AES, encrypt_AES 3 | 4 | class Server: 5 | """ 6 | A Mock server 7 | """ 8 | def __init__(self): 9 | if not glob("app/keys/RSA/*.pem"): 10 | # If the key is not already present inside keys direction, it is generated here. 11 | print("New rsa key generated") 12 | pr_key, pb_key = self.gen_RSA_key() 13 | save_RSA_key(pr_key, "app/keys/RSA/private.pem") 14 | save_RSA_key(pb_key, "app/keys/RSA/public.pem") 15 | 16 | # Loading the saved key 17 | self._pr_key = load_RSA_key("app/keys/RSA/private.pem") 18 | self._pb_key = load_RSA_key("app/keys/RSA/public.pem") 19 | 20 | @classmethod 21 | def gen_RSA_key(cls): 22 | pr_key, pb_key = generate_RSA_key() 23 | return pr_key, pb_key 24 | 25 | @property 26 | def public_key(self): 27 | return self._pb_key 28 | 29 | def get_AES(self, enc_AES:str) -> str: 30 | """ 31 | Decrypting the AES key which has been encrpted using server public key 32 | 33 | Args: 34 | enc_AES: encoded AES key 35 | 36 | Returns: 37 | Decrypted AES key using server private key 38 | """ 39 | AES_key = decrypt_RSA(self._pr_key, enc_AES) 40 | return AES_key 41 | 42 | def reply(self, ciphertext, enc_aes_key, nonce, tag): 43 | """ 44 | Response from the server. 45 | """ 46 | reply_dict = { 47 | "Who are you?": "I am secure data transmission service.", 48 | "What algorithms do you use?": "I use AES and RSA for encryption and decryption", 49 | } 50 | AES_key = self.get_AES(enc_aes_key) 51 | actual_message = decrypt_AES(AES_key, nonce, tag, ciphertext) 52 | reply_msg = actual_message + b" ==> " + bytes(reply_dict.get(actual_message.decode(), "Sorry, unable to answer this question."), encoding="utf-8") 53 | nonce_r, tag_r, ciphertext_r = encrypt_AES(AES_key, reply_msg) 54 | return nonce_r, tag_r, ciphertext_r 55 | 56 | --------------------------------------------------------------------------------