The purpose of SweetOnions is to emulate a smaller-scale version of onion routing using Python3.
There will be a client, server, directory, and three onion routing nodes through which the client can send and receive encrypted messages. Each message uses asymmetric encryption - the message itself is encrypted with 192-bit AES and the AES key is subsequently encrypted with 2048-RSA to ensure the sender is anonymized.
You absolutely need Python3 or higher. You may want to have docker too in order to test the onion routing with one machine.
If you have docker installed on your machine just ./launch.sh
and everything should work out of the box.
Otherwise this tool requires a minimum of five machines (2 onion routing nodes) and six machines (3 onion routing nodes) to operate in order to simulate a TOR/onion routing network. The machines should be running as follows:
Machine 1: ./client.py (This will request the user to enter the directory node's IP address as well as the message the user would like to send)
Machine 2: ./directory.py
Machine 3: ./node.py --generate-keys
Machine 4: ./node.py --generate-keys
Machine 5: ./node.py --generate-keys (Each node will request the directory node's IP address) [Optional Machine]
Machine 6: ./server.py
The following is a video demo of SweetOnions running across six machines: https://www.youtube.com/playlist?list=PLPNnD5CzODl0AT8zfREUCfGqaUXMoN9Dm
The following is a breakdown of what each aspect of the project accomplishes.
Firstly in a general manner :
- The directory server waits for the operational nodes addresses and public keys
- The nodes send their public keys at launch time
- When the directory has every key it sends the dictionnary to all nodes
- Then it sends the dictionnary for each client request
We will tend to use mainly bytes and cast to string when necessary
This is the front-end tool that allows users to send and receive messages from the server. Upon receiving the message from the server, the client will compare the hashes of the sent and received messages to ensure integrity.
The client must first contact the directory node in order to receive a list of potential onion routing nodes and their RSA public keys. The client will randomly select the path through which the message will be sent, and it will encrypt the message in the following manner, where Node 3 is the exit node and Node 1 is the entrance node:
a) AES Encrypt via Node 3's AES Key the following: [message + Node3_IP]
b) RSA Encrypt Node 3's AES Key with Node 3's public RSA key: [Node3_AESKey]
c) Concatenate the two encrypted messages - this is the inner most layer and the process will repeat two more times.
By the end of the encryption scheme, the following is the result:
Layer 1: AES[message + DestinationIP] + RSA[Node3_AESKey]
Layer 2: AES[AES[message + DesinationIP] + RSA[Node3_AESKey] + Node3_IP] + RSA[Node2_AESKey]
Layer 3: AES[AES[AES[message + DestinationIP] + RSA[Node3_AESKey] + Node2_IP] + RSA[Node2_AESKey] + Node1_IP] + RSA[Node1_AESKey]
It is the each node's responsibility to unwrap each layer via its RSA private key and continue to send the message along.
The directory node is designed to send the client (upon request) the list of node IP's and their corresponding public RSA keys. Before the client can make a valid request to the directory node, each onion routing node must first send its IP and its RSA public key to the directory - this is an initialization phase.
This represents each onion routing node (and has cases for both entrance and exit nodes) and must unwrap one layer of encryption and send the message along. The decryption occurs as follows:
Message Sent to Node: AES[AES[message + DestinationIP] + RSA[Node3_AESKey] + Node3_IP] + RSA[Node2_AESKey]
Node 2 uses its private RSA key to obtain the AES Key, and then uses that AES Key to encrypt the remaining contents. The result is:
Message Node 2 Sends to Node 3: AES[message + DestinationIP] + RSA[Node3_AESKey]
The purpose of server is to simply receive messages and send the hashed version of the message back to the original exit node.