This is a two part tutorial series for implementing End to End Encryption. The first part will be focusing solely on creating a server for generating shared key between two users.
What is End to End Encryption?
End to End encryption is a system of communication where only the communicating users can read the messages, thus preventing third-parties from accessing data while it's transferred from one end system or device to another. In E2EE, the data is encrypted on the sender's system or device and only the recipient is able to decrypt it.
Why use End to End Encryption?
End to end encryption brings a huge advantages: your data is protected against hacking or theft: even if a someone succeeds in intercepting your message while its being transfered, they will not be decipher the data from the message. Thus your app will be more secure from prying attackers.
Now we implement Diffie Hellman class. We will not be diving deep into how the algorithm works. To learn about that refer to this document
classDiffieHellman:# Current minimum recommendation is 2048 bit (group 14)
def__init__(self,group:int=14)->None:ifgroupnotinprimes:raiseValueError("Unsupported Group")self.prime=primes[group]["prime"]self.generator=primes[group]["generator"]self.__private_key=int(hexlify(urandom(32)),base=16)defget_private_key(self)->str:returnhex(self.__private_key)[2:]defgenerate_public_key(self)->str:public_key=pow(self.generator,self.__private_key,self.prime)returnhex(public_key)[2:]defis_valid_public_key(self,key:int)->bool:# check if the other public key is valid based on NIST SP800-56
if2<=keyandkey<=self.prime-2:ifpow(key,(self.prime-1)//2,self.prime)==1:returnTruereturnFalsedefgenerate_shared_key(self,other_key_str:str)->str:other_key=int(other_key_str,base=16)ifnotself.is_valid_public_key(other_key):raiseValueError("Invalid public key")shared_key=pow(other_key,self.__private_key,self.prime)returnsha256(str(shared_key).encode()).hexdigest()
Its possible that we send the keys over the network and don't have a Diffie Hellman object to call the method on. So let's add static methods to enable us to generate the shared key.
@staticmethoddefis_valid_public_key_static(local_private_key_str:str,remote_public_key_str:str,prime:int)->bool:# check if the other public key is valid based on NIST SP800-56
if2<=remote_public_key_strandremote_public_key_str<=prime-2:ifpow(remote_public_key_str,(prime-1)//2,prime)==1:returnTruereturnFalse@staticmethoddefgenerate_shared_key_static(local_private_key_str:str,remote_public_key_str:str,group:int=14)->str:local_private_key=int(local_private_key_str,base=16)remote_public_key=int(remote_public_key_str,base=16)prime=primes[group]["prime"]ifnotDiffieHellman.is_valid_public_key_static(local_private_key,remote_public_key,prime):raiseValueError("Invalid public key")shared_key=pow(remote_public_key,local_private_key,prime)returnsha256(str(shared_key).encode()).hexdigest()
It always a good practice to test out your code. We can test it out using the following snippet
>>>alice=DiffieHellman()>>>bob=DiffieHellman()>>>alice_private=alice.get_private_key()>>>alice_public=alice.generate_public_key()>>>bob_private=bob.get_private_key()>>>bob_public=bob.generate_public_key()>>># generating shared key using the DH object
>>>alice_shared=alice.generate_shared_key(bob_public)>>>bob_shared=bob.generate_shared_key(alice_public)>>>assertalice_shared==bob_shared>>># generating shared key using static methods
>>>alice_shared=DiffieHellman.generate_shared_key_static(...alice_private,bob_public...)>>>bob_shared=DiffieHellman.generate_shared_key_static(...bob_private,alice_public...)>>>assertalice_shared==bob_shared
Server Creation
Now that we are finished with the Diffie Hellman class let's setup the server.
Why an additional server?
This question might pop into your head, and its quite a resonable question as well. Well, in case of Cross Platform Apps written in more than 1 language, to ensure homogenous key generation algorithm, we delegate the task to a server. If you are using 1 programming language, then you can optimize the computation by using the key generation locally as using the server would only degrade performance.
We would be using Flask to create the server and also enable Cross-Origin Resource Sharing (CORS).
requirements.txt
flask
flask-cors
Install the required packages using pip install. Now let's create the server.
app.py
fromflaskimportFlask,jsonify,requestfromflask_corsimportCORSfromdhimportDiffieHellmanapp=Flask(__name__)cors=CORS(app)@app.route("/generate-keys",methods=["GET"])defgenerate_keys():dh=DiffieHellman()private_key,public_key=dh.get_private_key(),dh.gen_public_key()returnjsonify({"private_key":private_key,"public_key":public_key,})@app.route("/generate-shared-key",methods=["GET"])defgenerate_shared_key():try:local_private_key=request.args.get("local_private_key")remote_public_key=request.args.get("remote_public_key")shared_key=DiffieHellman.gen_shared_key_static(local_private_key,remote_public_key)except:returnjsonify({"message":"Invalid public key"}),400returnjsonify({"shared_key":shared_key})if__name__=="__main__":app.run()
Summary
The complete code looks like
To learn how to use the shared key to encrypt and decrypt messages, check out the second part