├── PRE_Candidate.py ├── README.md ├── demo.py └── requirements.txt /PRE_Candidate.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from npre import bbs98 4 | from npre import elliptic_curve as ec 5 | 6 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 7 | from cryptography.hazmat.backends import default_backend 8 | 9 | 10 | class CA: 11 | """ 12 | The CA that can be trusted 13 | Generate global parameters 14 | Distribute key pair for user 15 | """ 16 | 17 | def __init__(self): 18 | self.pre = bbs98.PRE() 19 | self.__keypair_dict = {} 20 | 21 | def generate_keypair(self, user): 22 | sk = self.pre.gen_priv(dtype=bytes) 23 | pk = self.pre.priv2pub(sk) 24 | self.__keypair_dict[user.index] = {"pk": pk, "sk": sk} 25 | 26 | def get_public_key(self, user): 27 | user_index = user.index 28 | if user_index not in self.__keypair_dict.keys(): 29 | self.generate_keypair(user) 30 | return self.__keypair_dict[user_index]["pk"] 31 | 32 | def get_secrete_key(self, user): 33 | user_index = user.index 34 | if user_index not in self.__keypair_dict.keys(): 35 | self.generate_keypair(user) 36 | return self.__keypair_dict[user_index]["sk"] 37 | 38 | def get_re_key(self, delegator, delegatee): 39 | sk_delegator = self.get_secrete_key(delegator) 40 | sk_delegatee = self.get_secrete_key(delegatee) 41 | return self.pre.rekey(sk_delegator, sk_delegatee) 42 | 43 | def get_param(self): 44 | return ec.serialize(self.pre.g) 45 | 46 | def __str__(self): 47 | return self.__keypair_dict.__str__() 48 | 49 | 50 | class Proxy(): 51 | """ 52 | The half-trustworthy Proxy(Server) 53 | Generate sym key(AES) on server to ensure security 54 | Store the encrypted message 55 | Take charge of re-encryption 56 | """ 57 | 58 | def __init__(self, param): 59 | self.pre = bbs98.PRE(g=param) 60 | self.pk_list = [] 61 | self.pre_aes_key = None 62 | self.pre_seed = None 63 | self.encrypted_data = None 64 | 65 | def register(self, user, ca): 66 | if user.index and user.index < len(self.pk_list): 67 | # This user already registered in Proxy 68 | return False 69 | else: 70 | # Assign index, request to CA Register in pk 71 | user.index = len(self.pk_list) 72 | ca.generate_keypair(user) 73 | self.pk_list.append(ca.get_public_key(user)) 74 | return True 75 | 76 | def aes_encrypt(self, data, pre_seed, user): 77 | self.pre_seed = pre_seed 78 | key = os.urandom(32) 79 | iv = os.urandom(16) 80 | self.pre_aes_key = user.get_encrypted_aes_key(key + iv) 81 | cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) 82 | encryptor = cipher.encryptor() 83 | self.encrypted_data = encryptor.update(data) + encryptor.finalize() 84 | return self.encrypted_data 85 | 86 | def aes_decrypt(self, aes_encrypted_data, aes_key): 87 | key = aes_key[0:32] 88 | iv = aes_key[32:] 89 | cipher = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()) 90 | decryptor = cipher.decryptor() 91 | return decryptor.update(aes_encrypted_data) + decryptor.finalize() 92 | 93 | def pre_reencrypt(self, rekey, pre_encrypted_data): 94 | return self.pre.reencrypt(rekey, pre_encrypted_data) 95 | 96 | 97 | class Client(): 98 | """ 99 | The client 100 | Side to side encryption/decryption 101 | Generate random seed for stream cipher(CHACHA20) 102 | """ 103 | 104 | def __init__(self, param, proxy, ca): 105 | self.pre = bbs98.PRE(g=param) 106 | self.index = None 107 | self.__random_seed = None 108 | self.__cipher = None 109 | self.__encryptor = None 110 | self.__decryptor = None 111 | # start registry 112 | proxy.register(self, ca) 113 | self.__sk = ca.get_secrete_key(self) 114 | self.__pk = ca.get_public_key(self) 115 | 116 | def generate_random_seed(self): 117 | key = os.urandom(32) 118 | nounce = os.urandom(16) 119 | self.__random_seed = key + nounce 120 | 121 | def get_random_seed(self): 122 | return self.__random_seed 123 | 124 | def init_chacha20(self, random_seed=None): 125 | if random_seed: 126 | self.__random_seed = random_seed 127 | else: 128 | self.generate_random_seed() 129 | algorithm = algorithms.ChaCha20(self.__random_seed[0:32], self.__random_seed[32:]) 130 | self.__cipher = Cipher(algorithm, mode=None, backend=default_backend()) 131 | self.__encryptor = self.__cipher.encryptor() 132 | self.__decryptor = self.__cipher.decryptor() 133 | 134 | def get_encryption_chacha20(self, data): 135 | return self.__encryptor.update(data) 136 | 137 | def get_decryption_chacha20(self, encrypted_data): 138 | return self.__decryptor.update(encrypted_data) 139 | 140 | def get_encrypted_seed(self): 141 | return self.__pre_encrypt(self.__random_seed) 142 | 143 | def get_decrypted_seed(self, encrypted_seed): 144 | return self.__pre_decrypt(encrypted_seed) 145 | 146 | def get_decrypted_aes_key(self, encrypted_aes_key): 147 | return self.__pre_decrypt(encrypted_aes_key) 148 | 149 | def get_encrypted_aes_key(self, aes_key): 150 | return self.__pre_encrypt(aes_key) 151 | 152 | def __pre_encrypt(self, data): 153 | return self.pre.encrypt(self.__pk, data) 154 | 155 | def __pre_decrypt(self, data): 156 | return self.pre.decrypt(self.__sk, data) 157 | 158 | def __str__(self): 159 | return (b"sk:" + self.__sk + b"pk:" + self.__pk).__str__() 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proxy Re-Encryption Demo in Python 2 | 3 | This is the demo of Proxy Re-Encryption(PRE) which demonstrates how it can be used properly combining with symmetric encryption to achieve extremely high security of user data privacy. 4 | 5 | In this case, stream cipher `CHACHA20` and Block Cipher `AES` is used to make sure the proxy(server) cannot get user's private key and the plain data stored on it. 6 | 7 | ## Usage 8 | ``` 9 | pip3 install -r requirements.txt 10 | python3 demo.py 11 | ``` 12 | ## Demo/Sample output 13 | 14 | ``` 15 | A ---------------------Request key pair----------------------> CA 16 | 17 | A <-----------------A key pair, global param------------------ CA 18 | 19 | B ---------------------Request key pair----------------------> CA 20 | 21 | B <-----------------B key pair, global param------------------ CA 22 | 23 | ---------------A want to restore something on the Proxy--------------- 24 | 25 | Plain msg sent from A: b'This is a demo' 26 | 27 | Seed from A: b'\x04+\xa4\xbe\xb9N\x95m(` 1H\x19\ 28 | xbf\xe6\n\xe89!\xc8I\xc3q\xd8]C:P\x 29 | da\xdeo\t\xd3\x9eR>\x1d\x19\xc4\xe3 30 | _@D\xdd\\\x8b\x0c' 31 | 32 | CHACHA20 msg sent from A: b'\x87\xe7))\x0bN^i\xb2\x03\xa1\x93 33 | \x97\x96' 34 | 35 | PRE encrpyted seed from A: b'\x93\xda\x00"\x01\x02\xf0\xe8\x9e 36 | \xa6FZ\xbd\xb9KN\x87:\xd5\xdb\x99\x 37 | 01\xe3\xadG\xc0\xfc|P&\xe5e\xa7\xbf 38 | }^E1\xda\x00"\x01\x02\x1044\x0c\x18 39 | \x11\xb0#\xa8\xc5=\x9e\xa2\xd8\x9c\ 40 | xb4\x8c\xc6\x98p\x93\x91\xfcdW,\xcd 41 | \xce\x8b\xe3j\x1d\xda\x00"\x01\x02\ 42 | xf1i\xc4\xa7\x94\xddJA\xb3\xe2B\xd6 43 | \xeeG\xa6\xf8?\x1c\x9e\x05(Q\ra\xae 44 | A\xeco\x1cV\x9f\xb4' 45 | 46 | A -------------(CHACHA20 msg,PRE encrypted seed)------------->PROXY 47 | 48 | AES encrypted data on Proxy: b'\xcd\xef\xb8\xc0h\xbclGC\x85\xbem 49 | O\xd1' 50 | 51 | ---------------The data is safely restored on the Proxy--------------- 52 | 53 | ------------Of course A can download and see it at anytime------------ 54 | 55 | A -------------------Request CHACHA 20 msg------------------->PROXY 56 | 57 | A <--(PRE_enc_seed, PRE_enc_aes_key), Request plain aes_key---PROXY 58 | 59 | Plain AES key re-decrypted by A: b'\x1a\xa4B\xd1\x94\x94\xdd\x15|,q\ 60 | xe6\x8f4\x8bk2M\x9c\x0c%\n\xbc}\xe2 61 | \x04\xa2\xe5\xa5\xdf\xd6\xec\xe8\xe 62 | 3T\x18\xd5\xd1\xbc\x18\xad\xc2\xcc\ 63 | x1b\xda\xbc\x03\xf3' 64 | 65 | a -----------------------plain aes_key----------------------->Proxy 66 | 67 | CHACHA20 msg decrypted by Proxy: b'\x87\xe7))\x0bN^i\xb2\x03\xa1\x93 68 | \x97\x96' 69 | 70 | A <-----------------------CHACHA 20 msg-----------------------Proxy 71 | 72 | Seed PRE decrypted by A: b'\x04+\xa4\xbe\xb9N\x95m(` 1H\x19\ 73 | xbf\xe6\n\xe89!\xc8I\xc3q\xd8]C:P\x 74 | da\xdeo\t\xd3\x9eR>\x1d\x19\xc4\xe3 75 | _@D\xdd\\\x8b\x0c' 76 | 77 | Plain msg received from A: b'This is a demo' 78 | 79 | -----------Now A successfully received the own correct data----------- 80 | 81 | -----------------------A want to send that to B----------------------- 82 | 83 | A -------------Request Re-encryption Key with B--------------> CA 84 | 85 | Re-encryption Key A->B: b'\x00\xbe\xe9\xfdi\xaa\xe7\xd3\xf8 86 | \xe8&\r\xc9\x8a^\x1af\xdeY<*I\x8b\x 87 | 0b\xbb.\x80\x8bR\xd6\xc4\xf3\xeb' 88 | 89 | A <------------------Re-encryption Key A->B------------------- CA 90 | 91 | A --------------------Re-encryption A->B--------------------->Proxy 92 | 93 | Re-encrypted seed: b'\x93\xda\x00"\x01\x02\x1d\xd8-t+\ 94 | xce\x05x\xd3\xdc\x0fg_\xaa;\xe3\x08 95 | \xfc\xa7\x10\xbb\xba\xafbeG\xb8\x1f 96 | \xa0\xf7\xea<\xda\x00"\x01\x02\x104 97 | 4\x0c\x18\x11\xb0#\xa8\xc5=\x9e\xa2 98 | \xd8\x9c\xb4\x8c\xc6\x98p\x93\x91\x 99 | fcdW,\xcd\xce\x8b\xe3j\x1d\xda\x00" 100 | \x01\x02\xf1i\xc4\xa7\x94\xddJA\xb3 101 | \xe2B\xd6\xeeG\xa6\xf8?\x1c\x9e\x05 102 | (Q\ra\xaeA\xeco\x1cV\x9f\xb4' 103 | 104 | Re-encrypted AES key: b'\x93\xda\x00"\x01\x03}>\xb9)5V\x1 105 | e\xa0\xc5\x8d)\x89\xd0+\xb6\xa4\xc1 106 | \x84w\x82\xadL\xe3@\xdd\x01\xcf\xd0 107 | "\xf8L\x10\xda\x00"\x01\x02\x8e\xaa 108 | \xe8\r\x8d% \xfe%<\x95\xa5~1\xd7\xe 109 | 6\xc0\xe7\x03\xfe\x1a>b_\x940\xeei\ 110 | xf9X\xa9E\xda\x00"\x01\x03\xdb\x9b\ 111 | x93Zt\x87-h\xe23\x08+\xd2\xa7\xa5A` 112 | *\xce\x19\x13\xcb\xae\xd6\xfe\xba\x 113 | c8\xdf\x1c.O\n' 114 | 115 | B <---(re_enc_seed, re_enc_aes_key), Request plain aes_key----Proxy 116 | 117 | Plain AES key re-decrypted by B: b'\x1a\xa4B\xd1\x94\x94\xdd\x15|,q\ 118 | xe6\x8f4\x8bk2M\x9c\x0c%\n\xbc}\xe2 119 | \x04\xa2\xe5\xa5\xdf\xd6\xec\xe8\xe 120 | 3T\x18\xd5\xd1\xbc\x18\xad\xc2\xcc\ 121 | x1b\xda\xbc\x03\xf3' 122 | 123 | B -----------------------plain aes_key----------------------->Proxy 124 | 125 | CHACHA20 msg decrypted by Proxy: b'\x87\xe7))\x0bN^i\xb2\x03\xa1\x93 126 | \x97\x96' 127 | 128 | B <-----------------------CHACHA 20 msg-----------------------Proxy 129 | 130 | Seed re-decrypted by B: b'\x04+\xa4\xbe\xb9N\x95m(` 1H\x19\ 131 | xbf\xe6\n\xe89!\xc8I\xc3q\xd8]C:P\x 132 | da\xdeo\t\xd3\x9eR>\x1d\x19\xc4\xe3 133 | _@D\xdd\\\x8b\x0c' 134 | 135 | Plain msg received from B: b'This is a demo' 136 | 137 | ---------Now B successfully received the correct data from A---------- 138 | 139 | ``` 140 | ## Advantage 141 | - The client(user) only needs to caculate stream cipher and PRE on keys, which is very fast. 142 | - Using symmetric encryption like CHACHA20 and AES, make the actual data size the same as the plain data, therefore the data stored on proxy(server) will be the same. 143 | - The proxy(server) knows nothing about the secrete key of any users and the actual plain data. What is actually stored is the data which has been encrypted twice (`CHACHA20` and `AES`), along with PRE encrypted`seed`(`CHACHA20` key) and `aes_key`. 144 | - All the data during the transmission is encrypted as well. 145 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | from math import floor, ceil 3 | 4 | from PRE_Candidate import CA, Proxy, Client 5 | 6 | 7 | def pretty_print(name, content): 8 | name = name.ljust(35) 9 | wrapper = textwrap.TextWrapper(initial_indent=name, width=70, 10 | subsequent_indent=' ' * len(name)) 11 | print(wrapper.fill(content) + '\n') 12 | 13 | 14 | def print_send_to(left, right, content): 15 | left = left.ljust(5) 16 | right = right.rjust(5) 17 | 18 | total_len = len(left) + len(right) + len(content) + 1 19 | part_len = (70 - total_len) / 2 20 | print(left + '-' * floor(part_len) + content + '-' * ceil(part_len) + '>' + right + '\n') 21 | 22 | 23 | def print_send_back(left, right, content): 24 | left = left.ljust(5) 25 | right = right.rjust(5) 26 | total_len = len(left) + len(right) + len(content) + 1 27 | part_len = (70 - total_len) / 2 28 | print(left + '<' + '-' * floor(part_len) + content + '-' * ceil(part_len) + right + '\n') 29 | 30 | 31 | def print_middle(content): 32 | part_len = (70 - len(content)) / 2 33 | print('-' * floor(part_len) + content + '-' * ceil(part_len) + '\n') 34 | 35 | 36 | """---------------------------------------Initialize------------------------------------------""" 37 | ca = CA() # create CA 38 | param = ca.get_param() # generate global parameter 39 | proxy = Proxy(param=param) # create Proxy(Server) with parameter 40 | a = Client(param=param, proxy=proxy, ca=ca) # create user A with global parameter and Proxy & CA 41 | b = Client(param=param, proxy=proxy, ca=ca) # create user B with global parameter and Proxy & CA 42 | print_send_to('A', 'CA', 'Request key pair') 43 | print_send_back('A', 'CA', 'A key pair, global param') 44 | print_send_to('B', 'CA', 'Request key pair') 45 | print_send_back('B', 'CA', 'B key pair, global param') 46 | 47 | """---------------------------------------A restore on Proxy------------------------------------------""" 48 | 49 | print_middle("A want to restore something on the Proxy") 50 | msg = b"This is a demo" # User A wants to send something to B 51 | pretty_print("Plain msg sent from A:", msg.__str__()) 52 | 53 | a.init_chacha20() # User A init chacha20,including generate random seed 54 | seed_a = a.get_random_seed() 55 | pretty_print("Seed from A:", seed_a.__str__()) 56 | 57 | msg_chacha20 = a.get_encryption_chacha20(msg) # User A encrypt it with seed_a using CHACHA20 58 | pretty_print("CHACHA20 msg sent from A:", msg_chacha20.__str__()) 59 | 60 | seed_pre_enc_a = a.get_encrypted_seed() # User A encrypt seed using PRE with A's public key 61 | pretty_print("PRE encrpyted seed from A:", seed_pre_enc_a.__str__()) 62 | print_send_to('A', 'PROXY', '(CHACHA20 msg,PRE encrypted seed)') 63 | 64 | aes_enc_msg = \ 65 | proxy.aes_encrypt(msg_chacha20, seed_pre_enc_a, a) # PROXY generate random AES key and encrypt CHACHA20 message 66 | pretty_print("AES encrypted data on Proxy:", aes_enc_msg.__str__()) 67 | print_middle("The data is safely restored on the Proxy") 68 | 69 | """---------------------------------------A download from Proxy------------------------------------------""" 70 | 71 | print_middle("Of course A can download and see it at anytime") 72 | print_send_to('A', 'PROXY', 'Request CHACHA 20 msg') 73 | print_send_back('A', 'PROXY', '(PRE_enc_seed, PRE_enc_aes_key), Request plain aes_key') 74 | 75 | aes_key_a = a.get_decrypted_aes_key(proxy.pre_aes_key) 76 | pretty_print("Plain AES key re-decrypted by A:", aes_key_a.__str__()) 77 | print_send_to('a', 'Proxy', 'plain aes_key') # A send plain aes key to Proxy for decryption 78 | 79 | msg_chacha20_a = proxy.aes_decrypt(proxy.encrypted_data, aes_key_a) # Proxy decrypted msg using AES and send back to B 80 | pretty_print("CHACHA20 msg decrypted by Proxy:", msg_chacha20_a.__str__()) 81 | print_send_back('A', 'Proxy', 'CHACHA 20 msg') # Proxy send CHACHA20 msg to A after decryption 82 | 83 | pre_dec_seed_a = a.get_decrypted_seed(proxy.pre_seed) # A decrypted pre_seed for plain seed 84 | pretty_print("Seed PRE decrypted by A:", pre_dec_seed_a.__str__()) 85 | a.init_chacha20(pre_dec_seed_a) # A initialize CHACHA20 by that seed 86 | msg_a = a.get_encryption_chacha20(msg_chacha20_a) 87 | pretty_print("Plain msg received from A:", msg_a.__str__()) 88 | print_middle("Now A successfully received the own correct data") 89 | 90 | """---------------------------------------A share it to B------------------------------------------""" 91 | print_middle("A want to send that to B") 92 | 93 | print_send_to('A', 'CA', 'Request Re-encryption Key with B') # A request rkey with B from CA 94 | 95 | rkey = ca.get_re_key(a, b) # CA cacluate re-encryption key from A to B,send it to A 96 | pretty_print("Re-encryption Key A->B:", rkey.__str__()) 97 | 98 | print_send_back('A', 'CA', 'Re-encryption Key A->B') # CA send re-encryption key to A after calculation 99 | print_send_to('A', 'Proxy', 'Re-encryption A->B') # A send re-encryption key to Proxy 100 | 101 | pre_re_enc_seed_b = proxy.pre_reencrypt(rkey, seed_pre_enc_a) # Proxy calculate re-encrypted seed for B 102 | pretty_print("Re-encrypted seed:", pre_re_enc_seed_b.__str__()) 103 | 104 | pre_re_enc_aes_key_b = proxy.pre_reencrypt(rkey, proxy.pre_aes_key) # Proxy calculate re-encrypted aes key for B 105 | pretty_print("Re-encrypted AES key:", pre_re_enc_aes_key_b.__str__()) 106 | 107 | """---------------------------------------B download it from Proxy------------------------------------------""" 108 | 109 | print_send_back('B', 'Proxy', 110 | '(re_enc_seed, re_enc_aes_key), Request plain aes_key') # CA send re-encryption key to A 111 | 112 | aes_key_b = b.get_decrypted_aes_key(pre_re_enc_aes_key_b) # B re-decrypted pre_re_enc_aes_key, send back to Proxy 113 | pretty_print("Plain AES key re-decrypted by B:", aes_key_b.__str__()) 114 | print_send_to('B', 'Proxy', 'plain aes_key') # B send plain aes key to Proxy for decryption 115 | 116 | msg_chacha20_b = proxy.aes_decrypt(proxy.encrypted_data, aes_key_b) # Proxy decrypted msg using AES and send back to B 117 | pretty_print("CHACHA20 msg decrypted by Proxy:", msg_chacha20_b.__str__()) 118 | print_send_back('B', 'Proxy', 'CHACHA 20 msg') # Proxy send CHACHA20 msg to B after decryption 119 | 120 | pre_dec_seed_b = b.get_decrypted_seed(pre_re_enc_seed_b) # B re-decrypted pre_re_enc_seed for plain seed 121 | pretty_print("Seed re-decrypted by B:", pre_dec_seed_b.__str__()) 122 | b.init_chacha20(pre_dec_seed_b) # B initialize CHACHA20 by that seed 123 | msg_b = b.get_encryption_chacha20(msg_chacha20_b) 124 | pretty_print("Plain msg received from B:", msg_b.__str__()) 125 | print_middle("Now B successfully received the correct data from A") 126 | 127 | assert msg == msg_a 128 | assert msg == msg_b 129 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==0.24.0 2 | cffi==1.11.5 3 | cryptography==2.2.2 4 | idna==2.7 5 | msgpack-python==0.5.6 6 | -e git+https://github.com/nucypher/nucypher-pre-python.git@9c76f56214fad18f0fa0acb396f1b03e10929a57#egg=npre 7 | pkg-resources==0.0.0 8 | pycparser==2.18 9 | pysha3==1.0.2 10 | six==1.11.0 11 | --------------------------------------------------------------------------------