In this article, you will see a prototype of an encrypted, Anonymous Network, which tries to resolve the problem of a compromised anonymous network.
Introduction
Encryption in Internetworking allows two or more entities to exchange information while hiding the content of the information itself. This means that anyone interested in the "conversation" cannot extract the information if it has not the necessary means (the keys) to decrypt the message in its original form. Encryption solves the problem of securing the information (confidentiality), but does not provide anonymity: this means that you always know that Alice is communicating with Bob, even if you don't know what they are speaking about.
Anonymous Networks allows to exchange information in both secure and anonymous ways. I want to point out that usually this is true for a viewer which is outside the network, which means that if somebody sniffs the traffic flowing between two nodes (PC, server or whatever), he/she cannot say from where the message is coming from nor where the message is going to.
But what if the anonymous network itself is compromised? One or more of the relaying nodes (which moves encrypted data) can fall in the hands of hackers or national agencies, which desire to break the anonymity and locate the origin and destination of the traffic flowing.
What I'll propose here is a prototype of an encrypted, Anonymous Network, which tries to resolve such a problem. Take into account that the application and idea provided are stubs, and can be improved on both logical and technical views; it's not my intention to provide a market-level utility for such idea (for the moment), but just to show that such an idea can work.
Background
I will explain, step-by-step, the design and implementation of such Anonymous network, by keeping all the concepts as simple as possible. This task alone will take lot of text (as you can see by the sliding bar), so I'll skip some notions on the used technologies while describing the software.
The reader must be confident with C programming language and all its basics (memory allocation, pointers, operators, etc.), and should have some background with socket programming and IP networks. Cryptography will be used in the project, so understanding of some encryption/decryption/authentication concepts are required.
The project will need OpenSSL library libcrypto
, version 1.1.0 or compatible; eventually you can easily modify the code for your version.
The software is developed for Linux, so also a background with such Operating System is required.
Index
- How the Onion Routing(tor) Protocol Works
- M2 Anonymous Network Idea
- Some Details of the Prototype
- Sniffing M2 Traffic
- Using the Prototype
- Final Considerations
TOR is a popular application which provides an Anonymous Network service. It works by relaying the traffic between the members of such network in a confidential way (encrypted) until a previously selected "border node" is reached and the traffic leaves the network (in a clear state, so unencrypted). This is made possible by performing a series of steps in order to prepare a flow inside the network, which are:
- Download/update the list of member of the network (from Dave, look at Picture 1).
- Select a path through this network for your data, by initializing some session keys in the nodes along the way. At the start, the packet is encapsulated multiple times with different encryption keys, which means that at every hop, the information is decrypted using a different key.
- Send the data along the newly created "flow". Every step consumes part of the packet (the header for that jump), until you reach the exit point (border node), and the data of the packet (encrypted with the border node session key previously setup) is decrypted and sent in clear on the internet.
Picture 1: Alice downloads the list of peers from Dave (bottom server), establishes a path to the bottom-right border node (number 3), and then relies data to Bob. Image source: https://www.torproject.org/
What happens here is that Alice contacts node 1, and establishes a node-to-node key with it. After this stage is done, another session key is exchanged with the node, and serves as decryption key for the actual data which will be exchanged. The node-to-node key in fact can be used later for other flows, while the session key will remain unique for the flow. Once the first step is done, Alice will contact node 2 through node 1. This is necessary so node 2 won't realize that is Alice the one establishing the route, and this mechanism is essential to maintain Anonymity. The operation then repeats: a node-to-node session is established for node 1 to node 2 communication (which can be used for multiple flows), and a session key is exchanged between Alice (masked by node 1) and node 2 for the data decryption.
Figure 2: The original message is encapsulated inside different layers of encryption (the "Onion network" name comes from this characteristic), one for every hop that the message must do in order to reach its destination. Image source: https://security.stackexchange.com/
Now, session keys are exchanged through the tunnel while it is created piece after piece; at every step, the node which is accepting it just realize which is the previous node and who will be next one in the flow, but won't have a picture of the whole circuit. Even if node 2 doesn't know exactly who is the source, it is still able to isolate a source for a flow from another one, meaning that it can classify various flows of traffic.
A common attack to the TOR network is sniffing the traffic at the border node (Image 1, node 3), which means you know what is the information which traveled encrypted (loss of confidentiality). If the border node (or the relaying nodes which form the network) is also compromised/hacked, then you can also achieve the loss of anonymity, since you will have the ability to look at the traffic while it reaches the node. Remember that the exchange of the session key for the decryption of the traffic uniquely identifies the flow along all the route, even if you don't have the exact source.
Figure 3: The Nodes 1, 2 and 3 exchanged session keys for encryption/decryption of information with the TOR proxy, thus they know where the information is coming from if they can decrypt it. Source: https://www.youtube.com/watch?v=LAcGiLL4OZU
TOR is already doing a great job in protecting peoples which wants to communicate from countries where human right are denied and military/politics states negate the liberty of expression, but the network seems to not be able to protect itself from within. This is, of course, a particular case and a particular attack; the monitoring of such network is possible only if a good amount of nodes start to be hacked or introduced by the attacker. If a security agency starts to inject in TOR dirty nodes (totally legit since such network is made of volunteers), then it will enhance the probability of discover who is the original sender of the message. If (even worse) such agency also controls a good set of border nodes, then it can both recover sources and messages that are exchanged.
As I previously said, Anonymous networks need two requisites to work: confidentiality and anonymity.
How can we preserve both for the information exchange between Alice an Bob?
Let me start this discussion by considering the following points as true:
- Alice and Bob (end-point receivers) of the communication are not hacked/compromised. This is important, otherwise it does not matter which cryptography or trick you put in place, the traffic can be sniffed even before it leaves the application memory area.
- Alice can identify Bob, and vice versa. Alice obtained in a trusted way a certificate/key to identify Bob by a trusted third party. If somebody operates a man-in-the-middle attack, by faking Bob and Alice identity successfully (by stealing their private keys), well... there is little you can do.
In the following Anonymous network (let's call it M2, as the prototype name), encryption of the data exchanged between Alice and Bob is performed end-to-end, with keys known only by Alice and Bob. It is Bob and Alice duty and responsibility to decide what to do with such data after they receive it (relaying in clear to the Internet). No relaying nodes will ever know such key, so they will be not able to decode the information as it is moving through the network.
Figure 4: Alice uses a key exchanged with Bob to perform end-to-end cryptography and keep the message safe as it travel in the network.
This way, as long as Alice and Bob don't reveal the message, confidentiality is preserved.
Now, for anonymity, we must hide the source of the message/packet from the relaying network itself. Destination is mandatory, otherwise the message cannot be correctly dispatched to Bob (and the reply to Alice), but the source is not necessary at all. During the session key exchange phase, Alice and Bob select the key and a Session ID, which will be used to identify the correct encryption/decryption key during message exchange. Note that such ID is not fixed (as the source address actually is) and can be replaced as Alice/Bob desire by restarting the session key exchange phase.
In addition to that, messages exchange within the network is done by using different keys at every link between the nodes. When the communication starts, and a node connects to another, it performs a sort of handshake procedure where a common key is selected and exchanged in order to perform an efficient encryption/decryption (by using AES). This cryptographic key is valid only for this link, and if the same node connects to another one, the procedure is triggered again and another key is established. All the traffic flowing between these particular two nodes will be done by using such key, which could be renewed at any time by one of the two members of the channel.
Figure 5: Alice and Bob cannot be distinguished in the network; they are any different from the other nodes. Each communication between all such nodes uses it's own cryptographic key. Network topology can be also not fully connected, if a routing protocol is there to help message delivery.
With M2 network, I also don't want to distinguish nodes with a particular job; anyone can be either a border node or a relaying node. It is up to nodes to decide where to start and finish the communication, and if to move such traffic to the Internet. This creates no special target for attackers, which have to take into account every single node, and not just a particular set of this private network. At the end of the day, Alice and Bob will both exchange data (for their own communication) AND relaying data of other conversations (without knowing from where such data is coming). By doing that, you ensure that traffic which flows out of Alice is not just traffic created by Alice, and a sniffer cannot distinguish between the two types (created by Alice or relayed by Alice).
Let's see what ideally happens to the traffic which flows through the nodes.
From the outside of the M2 nothing particular happens; the traffic of the network is encrypted and not readable if great efforts are put in order to decipher the hidden information. If this is done, just a part of information is discovered since the real data is again hidden by using another different key. The only visible parts are the outer IP/TCP headers, which provides transmission control and location of the previous hop (which could be a relaying one, so not the original source) and the next one (which could be a relaying one again, and so not the original destination).
Figure 6: Packet as seen by an outside sniffer.
The difference here is how the packet is perceived by a relaying node itself? Remember that in this network, no session has been established with the original source in order to decipher the information, and the decryption phase is done by using keys exchanged during the connection procedures with the direct neighbours (nodes directly connected with me). Well, when the information is received and encrypted, what remains visible inside the arrived packets is just another header containing the destination of the packet and an identifier. Some other elements can be present, but they are not relevant in determining the source/destination.
Figure 7: Packet as seen when relaying is performed.
The node with such information can now follow his policies and routing strategies, since the source did not pick the route to be strictly followed by every node in the network. Adapting to the actual network congestion, following the desired policies on traffic management, the node which have to relay the information is fully independent on taking a decision on what to do. The only information it can use to perform such decision are the destination name and a meaningless id (meaningless to him, since it can change over packets).
I want also to note that no central authority is necessary here (see figure 1, Dave role which exchange the list of active peers). Alice is free to pick and decide who is trustworthy enough to relay her traffic, and can decide to add/remove at any time element of this list. Hacking Dave has no effects on this network because Dave is no part of the network, and cannot deliver to us a list of hacked nodes.
The designed network will not have good performances in term of Round Trip Time, since the data is encrypted and decrypted multiple times as it flows along the network. Performances actually is not one of my concerns here, since a better designed application (the one presented here is a prototype) and adjustments to the network behavior can improve latency of the traffic. The main objective here is to preserve the Anonymity, so for the moment I'm fine with losing some performances as a price to pay. Take into account that you don't have to Anonymize all your traffic, but only in some set of it; browsing can still occur normally, while using a chat system on a port/address must be secured; wisely using routing rules in your PC allows both the options to be online at the same time.
The first important point I want to highlight is that the application is a prototype written during free time, so it's is not ready to be used in commercial/real environments. If I could dedicate more time, It would have been more appropriate. In any case, the source is attached to the article, so just grab it and modify them as you need.
Bootstrap
When you run M2, it will behave like a normal console application and it starts to scan the command line for orders. The application has a default behavior, which is using the address 127.0.0.1, since the local loop is usually present inside a Linux distribution. This can be changed by specifying the --addr <ip>
option, which will instruct M2 to operate with a different address (IPv6 should work, but has never being tested).
If no other options are present, at initialization stage M2 looks for a nodes files, and lists the addresses and ports of the "trusted peers". By modifying this file and inserting more lines, you increase the number of peers which are connected to you, and the possible routes you can take to go to your destination. If you decide to put the application in passive mode, where it only accepts incoming connection, --no-nodes
option can be pass to the command line. This way, M2 will do nothing more than stay silent and accept incoming connection with other nodes.
void node_load(
nstore * ns, kstore * ks, char * path, netloc * nl, int nof_locs)
{
int i = 0;
char buf[4096] = {0};
char * tok = 0;
char addr[INET6_ADDRSTRLEN] = {0};
uint16_t port = 0;
node * n = {0};
FILE * f = fopen(path, "r");
if(!f) {
log_info("No known nodes detected\n");
return;
}
log_info("Loading known nodes:\n");
while(fgets(buf, 4096, f)) {
tok = strtok(buf, " ");
strncpy(addr, tok, INET6_ADDRSTRLEN);
tok = strtok(0, " ");
port = atoi(tok);
node_lock();
for(i = 0; i < ns->size; i++) {
if(!ns->net[i].thread) {
n = &ns->net[i];
break;
}
}
node_unlock();
if(i == ns->size) {
log_err("Network slots exhausted!\n");
return;
}
memset(n, 0, sizeof(node));
nl_from_string(&n->loc, addr, port);
n->fix = 1;
n->RSA = ks_load_node(ks, &n->loc, 1);
n->kstore = ks;
n->nstore = ns;
if(!n->RSA) {
continue;
}
log_info("Node at %s:%d discovered\n", addr, port);
node_start(n, nl, nof_locs);
}
fclose(f);
}
From the previous code snippet, you can deduce a bunch of points of the application:
- M2 maintains a "node store", which is a logically organized set of nodes which it knows. Such nodes will form the trusted network that will be used.
- M2 maintains a "key store", which aggregates the key used by the application. It contains RSA public keys used to authenticate nodes (if you don't put in M2 folder the public key of the trusted nodes, no connection will be established with them), the AES session key used for node-to-node connection and session key used for communication between two far nodes. This last set contains keys and Session IDs that will be used to decrypt a packet which is delivered for us (not to relay).
- Addresses are abstracted with a netloc structure, which allows us to use IPv4 and IPv6 address within a single element. Internally, M2 will use this instead of going for raw addresses. This is an useful abstraction to later extend it over multiple technologies, like Raw Ethernet, for example.
typedef struct network_location {
int type;
union {
struct sockaddr_in in4;
struct sockaddr_in6 in6;
};
} netloc;
- Fix flag present in the node description triggers the reconnection mechanism. If a node is passive (or has not the peer present in its nodes list), it will not attempt to reconnect to that remote endpoint: it's the duty of the original requester to attempt again to connect with us.
- RSA public key will be loaded for the node. This is used during the authentication stage, since the first messages will be encrypted using such key. Failing to load it will skip this connection, and the selected slot will be reused for the next element in the nodes file. Don't add public keys of nodes you don't trust.
If everything goes fine, the application starts a thread which will serve such connection. On disconnection, the thread won't die and will keep trying to reconnect until you shut down M2.
Communication with Neighbors
The core of the behavior for a thread in M2 is in node.c file: the node_loop
procedure will continuously loop until the connection is closed (and the node is not "fixed"), or until the user order the application to stop using Ctrl-C signal. This thread initializes the structures and buffer used by the thread alone, and will not try to lock common resources until certain actions are triggered. With such an approach, we can reduce concurrency at minimum, but remember that this is a prototype and could be written in a better way.
void * node_loop(void * args)
{
node * n = (node *)args;
[...]
n->recv_buf = malloc(sizeof(char) * M2_BUF_SIZE);
n->recv_de = malloc(sizeof(char) * M2_BUF_SIZE);
n->send_buf = malloc(sizeof(char) * M2_BUF_SIZE);
n->send_en = malloc(sizeof(char) * M2_BUF_SIZE);
n->lo_buf = malloc(sizeof(char) * M2_BUF_SIZE);
n->lo_data = malloc(sizeof(char) * M2_BUF_SIZE);
if(!n->recv_buf ||
!n->recv_de ||
!n->send_buf ||
!n->send_en ||
!n->lo_buf ||
!n->lo_data) {
log_err("No more memory\n");
goto out;
}
[...]
while(!n->stop) {
[...]
}
As we previously said, if the node is marked as "fixed" (entry read from the nodes file), then the thread does not die and retries to connect with the remote host if the socket is closed by an error. If such event happens, the thread restarts the connection and will try to setup a session key for the communication over the new socket. By default, all the traffic flowing here will be encrypted/decrypted using AES cryptography with 256 bits keys. The key and Input Vector will be selected randomly using OpenSSL random routines.
if(n->sockfd <= 0) {
if(!n->fix) {
goto out;
}
if(!node_reconnect(n)) {
usleep(NODE_KA_TIMEOUT * 1000);
continue;
}
log_info("(Re)Connected to %s:%u\n", addr, port);
if(node_authenticate(n)) {
goto sleep;
}
}
An important note is that you always have to remember to seed properly the random routines. This is not done by using srand(time(0))
as you always find around the lot of old articles: it is not cryptographically safe. What you have to do instead is to seed using the MD5 hash (or another kind of uni-directional function) of the time itself. This is done in M2 during initialization steps in main.c.
time_t t = time(0);
[...]
MD5_Init(&md5ctx);
MD5_Update(&md5ctx, &t, sizeof(time_t));
MD5_Final((unsigned char *)md5dig, &md5ctx);
RAND_poll();
RAND_seed(md5dig, 16);
Since M2 uses TCP stream, what happens next in the node loop is data retrieval: the thread reads the first 4 bytes, which contains the clear (not encrypted) length of the data which follows, and then reads the message itself. During this collection, if no data has been read (DONTWAIT
option set in the read operation on the socket), then a keep-alive mechanism is triggered and I/O is tested from the virtual devices that are created by the application (will come to this later in the article). This will check if the socket is still open and ready or has been closed by the system.
Finally, at last, when the entire message is collected, the last lines or the loop performs decryption of the message, depending on the status of the link keys. If they have been revoked, the thread fall-back to RSA encryption/decryption by default (which is very resource consuming) to avoid data loss.
Figure 8: The simplified state machine of the node main loop; green arcs are answer "yes" to node questions, while red nodes are "no". One way direction have a gray color. "Bread" indicates the bytes read from the socket. On error the state machine closes the socket an restart the connection again.
Layered Approach
Organizing the application in layers and modules is always useful to keep the project in a good shape. Modules can be modified/exchanged without having twisted dependencies to maintain, and layers allows to reduce complexity from a logic task to another. I learnt layering in Internetworking with RINA, and I guess it will never forget such approach.
M2 is organized in two different layers for communication, and they have different domains in the communication cycle. The lower layer performs node-to-node data exchange, and also provides the basic authentication to trust a remote node and exchange a "link key". This layer does not know anything of how it will be used to, and the upper layer will not care how the job is performed, as long as it is correctly completed.
Figure 9: Layers present in M2 prototype. The, upper, Logic layer is in charge of exchanging data and perform the routing, while the lower one is in charge of the delivery and session authentication. End-to-end encryption between two nodes is part of the logic layer too.
This approach allows the application to separate different mechanism, which can be later improved/changed without messing with other components of the application. This means that it is possible for the logical layer to change protocol format and behavior without having the Node-layer realizing it (and vice-versa, of course). Keeping such organization is essential to deal with increasing complexity of the code.
Actually no routing is present in M2, and it needs to be directly connected with all the nodes of its network. A possible improvement to this architecture can be the introduction of a logic-layer message which is in charge of exchange encrypted routing table, from which a path to the final destination of the packet can be reached.
Protocols and Messages
Messages exchanged by M2 network reside in the proto.h header, and can be distinguished by the layer where they live in. The "lowest" messages which are exchanged by M2 are Node-layer messages. Seen by an entity placed outside the trusted network, such information travels with the following format:
Some bytes (four, for the moment) at the head identify the length of the next message that will be exchanged, while the following information will be encrypted and not readable without the proper keys. Once the message is retrieved by the node, decryption is applied on the received (complete) packet. This is done by operating a simple choice (for the moment): if Link keys have been established, AES cryptography will be used, otherwise the message is decoded by using the node RSA private key.
int node_encrypt(node * n, int rsa, char * buf, char * en, int size)
{
#ifdef EBUG
if(crypt_disable) {
memcpy(en, buf, size);
return size;
}
#endif
if(rsa) {
return crypt_RSA_pub_encrypt(
n->RSA, buf, en, size, RSA_PKCS1_OAEP_PADDING);
} else {
return crypt_AEScbc_encrypt(
n->link->key,
n->link->iv,
&n->link->en_key,
buf,
en,
size);
}
return -1;
}
As I previously said, the node always tries to exchange and setup a session as a first operation, so the first messages exchanged are encrypted using the receiver public key, and the receiver decrypts it using the private one (this grant that he will be the only one able to decrypt it). Included in this information.
Decrypted Node-layer traffic has the following format:
The header contains meaningful information about the data which follows it, and identifies the size of prefix and postfix of the message. These two random-size arrays (containing random data) are useful to hide the real dimension of the packet, since traffic can be easily classified by using its size. The hash field acts as a basic input validation tool, and helps to identify valid data from random traffic which can be received by the node (you cannot distinguish random data from the encrypted one as long as you don't try to decrypt it... and you can decrypt random data).
typedef struct proto_head {
uint8_t type;
uint32_t len;
uint16_t pre;
uint16_t post;
}__attribute__((packed)) p_head;
Messages exchanged have different types, which are:
enum proto_types {
PROTO_INVALID = 0,
PROTO_NODE_SE,
PROTO_NODE_KA,
PROTO_NODE_LO,
PROTO_LOGIC_SE,
PROTO_LOGIC_RT,
};
Node-layer messages perform Link authentication (node to neighbor) and keep-alive servicing, checking if the connection is still valid to be used. Such mechanisms are resumed in the state machine pictured in figure 8. Session establishment simply works by exchanging and agreeing on a set of Key and IV used to later encrypt/decrypt data using AES cryptography.
Logic layer messages are encapsulated in Logic-layer packets, and have the following format:
Again, an header identify the type of message exchanged. This is a simplified version of Node-layer messaging, since lot of services are already performed by the lower layer. Session establishment works as the lower one, with the difference that the agreement now includes the selection of an ID used at destination to pick the right key to decrypt the incoming messages.
typedef struct proto_logic_se {
uint8_t op;
uint32_t id;
unsigned char key [CRYPT_AES_BYTES];
unsigned char iv [CRYPT_AES_BYTES];
}__attribute__((packed)) l_se;
Finally, data exchanged between Alice an Bob have the following format:
and is relayed by intermediate nodes before reaching Bob. During this transaction, as I said previously, relaying nodes have no way to identify who is the real source of the traffic, since no keys have been exchanged between the source and the intermediate node. The "route data" message have also a simple trick which allows to add more randomness to the network, which is the random delivery. This works because no routing is in place and I assume a fully connected network between the various nodes; the mechanism can of course change once a proper routing is introduced in the network.
typedef struct proto_logic_route {
uint32_t id;
uint8_t credit;
uint8_t type;
char dest[PROTO_LOGIC_ROUTE_LEN];
uint32_t size;
}__attribute__((packed)) l_rt;
The credit field of the packet works as a Time-to-Live property of the packet, and as long as it is not 0, nodes keep selecting a random next hop and then they deliver the traffic to them. When the credit is exhausted, the packet is shipped to the real destination, which is supposed to have the necessary keys to decrypt it.
The following random routing strategy has for sure some problem when handling TCP traffic, since one link can be slower than another, and packets can start to be delivered out-of-order. Extending M2 to change routing policy or to provide the necessary re-ordering is just a matter of implementation, and thanks to the layering architecture, it should be easy enough to introduce it without messing with other components.
Input/Output of M2
M2 works by creating a virtual interface for each end-to-end session established. This means that you can move in this network any traffic which can travel over/with IP. Since the virtual interface (TUN device) can be associated with an IP, or you can easily modify routing rules to re-route a category of traffic on such device. You can actually decide which type of information must travel encrypted and anonymous, while keeping your favorite streaming or online gaming application running normally on the common internet.
At the end, the traffic moved from M2 instance to another M2 instance is:
M2 comes with a debugging feature which disables any encryption/decryption routine applied to the data, while preserving all the other functionalities. If you compile the project with the -DEBUG
flag, you will be able to sniff the traffic with an application like Tcpdump
and see the unencrypted messages flowing. This feature is of course dangerous and shall not be integrated in the release version of the software, as it break anonymity and confidentiality.
Once a connection with a neighbor peer node is established, a Link-session request message leaves the node in order to configure a common, more performant, AES encryption/decryption key.
11:10:24.648202 IP localhost.48390 > debian.9000: Flags [P.],
seq 1:700, ack 1, win 342, options [nop,nop,TS val 64478 ecr 64476], length 699
0x0000: 0000 0000 0000 0000 0000 0000 0800 4500
0x0010: 02ef d1b0 4000 4006 6855 7f00 0001 7f00
0x0020: 0002 bd06 2328 43fc 2a73 1385 4898 8018
0x0030: 0156 00e5 0000 0101 080a 0000 fbde 0000
0x0040: fbdc 0000 02b7 0100 0002 4100 1d00 10.c9<-- Lower layer header + random prefix c9bfb7...
0x0050: bfb7 f99b 909c c500 c426 b483 a426 f517
0x0060: 7868 bfb4 fe58 45d5 7c71 305d 01.16 b21f<-- Request(0x1) + AES key: 16b21f....
0x0070: 6ff4 5bd3 562a f67e f122 3252 4397 af43
0x0080: f741 441e 77eb 98b8 6cc6 1f48 4f.2a 8d6d<-- AES IV: 2a8d6d...
0x0090: db00 f321 bd7a adfe 4553 567c 0cbd f747
0x00a0: 7d91 f345 b4d4 59f8 26f5 ff87 7c.32 a5b6<-- Authentication of sender: 32a5b6...
0x00b0: 043e 3b14 a3b1 d143 7697 f0cd aca2 fab3
0x00c0: d7a2 8a7b add4 b0e5 d68f ed05 403d a726
0x00d0: 30e6 ff79 e3ec 652e bbce ed19 6605 b0e8
0x00e0: 2719 6717 5be5 fc58 238f 4007 a598 cd82
0x00f0: f0fc 4d41 2647 0e30 4653 a588 ca27 1b8f
0x0100: 52ae d4a6 ad00 aedf ee2f 1a1b 1dcd ddef
0x0110: 041c 92f2 acff e198 e754 a943 df2b a766
0x0120: 0827 431b 3ab2 9afa 2c05 7186 e415 05f2
0x0130: 17f8 0ffd 2a5c 8ab5 68dd 13bf e072 4a63
0x0140: 25c6 54c1 7d2c f8a8 ce71 0451 da4e abd3
0x0150: 92ae 6634 72db 7106 bb38 ad83 9443 d2a4
0x0160: 0a15 8ece 37fd 15aa 21d3 08e6 174c b062
0x0170: 881b a925 b25c fbed d4bb c432 9c65 30ef
0x0180: 9ea0 3f07 7bba b6f9 d89f 0602 438d 1d65
0x0190: cda6 5877 8ce0 3f6a e1ea db82 677a 32ce
0x01a0: a5d1 484c 15aa f54c 43fc a9a9 0252 0ffe
0x01b0: 3ac0 0d90 0e5f dc7d 591e 3a4a 218a 12cd
0x01c0: 1ef0 b96f e4fe 04f5 2102 2f0f 3708 6088
0x01d0: 6e5c b4c7 4603 6922 e606 00a3 767b 3b8a
0x01e0: 04df 2f52 0611 683b a366 44c9 c63a f0a3
0x01f0: 5ea1 be94 9faa 43fd 6bba 21de d4f0 715b
0x0200: a222 d0e8 87d9 ce1d 4818 3bcc fc3f 94fc
0x0210: 87bb f4a4 7c21 d29d e02a 3300 363a 4015
0x0220: 4465 2015 26d0 5f98 1a6e 66f0 2808 c1cd
0x0230: e04c df9b 3e32 093e b4e1 6826 2459 e055
0x0240: f461 ebf8 62bf add4 ab46 0891 220d 7441
0x0250: b521 5a15 9eb3 4f1b 427c 88b2 af09 2001
0x0260: 9d42 b415 4b58 e418 9bf9 f849 3b06 6a69
0x0270: 968d 5ce3 e878 480b b100 b418 eac0 300c
0x0280: 58e4 bd5e 4c2a 5df2 b089 7482 91bf 459c
0x0290: e6f7 14ae 7a59 b16e 5127 c233 3069 1d69
0x02a0: e9ca 2c40 5f55 70d7 bac1 886a 7f.93 1084<-- Final hash: 931084...
0x02b0: 1bcd 9cd7 0cb6 afa8 adb8 1546 637f 546c
0x02c0: 871d 2acb 4a4b b18e 48a7 799f fb95 163f
0x02d0: 664d 9cce 7c8d 521e a2b4 967f b06c a96e
0x02e0: 3db7 7b4e 6c21 5373 6539 d7ca 88.e5 cd33<-- Random Postfix: e5cd33...
0x02f0: f7fd bcf1 257a dec0 90e1 e9bf 3a
Highlighted in this packet, you can see the interesting part of the message, stating with the Node-layer header, which indicates the message type 0x01, the size of the internal data (0x2b7) , the random prefix length (0x1d) end the random postfix length (0x10). After the header comes the prefix random data, followed by the proto_session_key
message structure.
The first field is the operation, which is a request(0x01), followed by 32 bytes of the AES key and 32 bytes of IV. I separated the three fields of this message using a dot, which is not present in a normal Tcpdump
output. After the structures come the authentication hash, encrypted with the sender private key, which can be decrypted using the public one; this is necessary to be sure to identify the source of the session request. Finally, a hash performs the base data validation, and closing the message, you can find the random postfix.
The same type of message, with encryption enabled, will be sniffed as the follows trace:
15:07:57.271607 IP localhost.39968 > debian.9000: Flags [P.],
seq 1:1029, ack 1, win 342, options [nop,nop,TS val 6546 ecr 6543], length 1028
0x0000: 0000 0000 0000 0000 0000 0000 0800 4500
0x0010: 0438 de11 4000 4006 5aab 7f00 0001 7f00
0x0020: 0002 9c20 2328 f545 c781 0797 5afc 8018
0x0030: 0156 022e 0000 0101 080a 0000 1992 0000
0x0040: 198f 0000 0400 2ff7 6d6a b7e7 35ba f3.c1<-- Lower-layer head (9 bytes) + ??? prefix
0x0050: a5cf ce58 79e7 6aec 4d65 c584 2c30 819c
0x0060: 321f 3018 cc34 71e6 f550 c8f8 b8f9 b4cb
0x0070: da48 cb83 1a4d 16f7 2438 2119 694e 3c7b
0x0080: 172e 898e e895 4272 c11a e2e3 c450 eddf
0x0090: 449b 605b 6b5e bf84 2fd1 583d 29a8 0c6a
0x00a0: 147a 89a9 7a04 0883 d63f b9b4 fd9a 8a2e
0x00b0: e4a6 c702 98e4 9cf4 8f41 35c8 5248 b77c
0x00c0: 0b03 3887 519c 424d bc06 5e26 70a4 65c3
0x00d0: 8ece 719b 20fa 895d a3ae 8cb3 7a80 dd2c
0x00e0: 9bf5 bed3 dcd5 e04a 98dd f6f3 2e2e 4c3e
0x00f0: 9c13 a10f cb4f a1f8 b0f3 b441 b66c 34e9
0x0100: 4ab3 d2d2 81b5 fae0 fbd6 ceac ef56 4119
0x0110: 4d38 d0c5 73d3 62c4 ac35 f859 e84d dd2d
0x0120: 24c3 14bb 94b9 1a6a a742 4dbd 383a 696f
0x0130: 4f2e 6361 16a4 4cb2 6bf5 cecd 1b7a 9364
0x0140: 3ae4 7d3b 2b72 5d3d 18c9 a58c 115c 7500
0x0150: 7cab 0793 ff8a 51bd 801e 594d d69c 58d8
0x0160: 7316 8734 87df aea6 2c8d b640 c98c 9330
0x0170: 06db 2298 4327 70f1 03fc 65d4 32f4 f11d
0x0180: b3e8 19be e6bd 5727 4e21 2910 fbd2 d8eb
0x0190: 3cff 66c4 fed3 a04a 43b2 b88b f4fa c0df
0x01a0: 2bd7 e4e6 67e0 4d35 c393 4f37 1210 07a6
0x01b0: 8fdd 84fd 39ff 6b65 3d04 ecc7 4b17 8705
0x01c0: 1403 359f 722e 83f1 1fe4 e3c6 30bc e87e
0x01d0: 7347 03c9 8cdc e835 5184 5059 69b1 055f
0x01e0: 018d 9f16 92e6 d93b 963d b145 681f ffce
0x01f0: d531 ed0e 085e a9e0 727e d926 1838 b292
0x0200: 1ede 5d59 7afb c9e1 985d b65f 8485 c517
0x0210: ef1d 7538 8ea4 8490 16ff 4567 0203 ce9e
0x0220: a82b 6b33 a065 3d3c 0160 e967 98a3 0e5a
0x0230: 5f4d 02c0 b9b9 f907 8408 871a 9408 cb2c
0x0240: b13c 758e 9c2e b14e b635 5c26 610c 8978
0x0250: 65c9 12c5 d050 60ee 2f28 74d3 c9e3 d433
0x0260: cfa8 9604 bf2e a444 424d c0f7 298c 11f3
0x0270: f8aa 8ff1 b319 73ea 9c99 3445 c38d a12f
0x0280: 403c 1bea 49ae 6765 aa21 ca61 a507 aca4
0x0290: 0287 4f18 f78a 68f0 af55 041a 95f4 879f
0x02a0: 8e07 e1ba 38c2 be44 78bd 4f47 09e1 0621
0x02b0: 2346 9ca6 0a38 4993 508f 9200 e4f4 a92d
0x02c0: a7fa 12af f378 ac29 a56a b904 c0b0 a1da
0x02d0: 5913 c136 ecff ea42 301c a396 c138 e94e
0x02e0: 2979 648b 00c4 007b 5090 b643 e4d4 cbac
0x02f0: b7f3 d466 5fbe 9caf a5a3 5c49 3f5c 2ae7
0x0300: 68bc 9528 b2a9 e0d7 dbeb 29cb bac8 47c6
0x0310: cf92 ca3f 965d f755 d6dc 8e94 f7ca c2d2
0x0320: 42f4 0e8d 4d8c 6888 0985 7e65 18bc 6443
0x0330: 38d2 6791 3582 9579 6eb4 6931 b4f4 4082
0x0340: 28b3 5666 b0ac 780c e563 5961 f00d 884c
0x0350: 4122 0f3e 75c7 779b e548 e1e4 5780 a605
0x0360: b243 f9bf 3ddc aa5d 0c4b 5fdb 5745 f06e
0x0370: 5ecc 41da f6c4 c61a 3967 3548 6ffd 043a
0x0380: 550d e9d7 291c 5e3e 8720 051e 8640 3255
0x0390: 657a 513a b594 4e81 c146 b3d0 9291 5968
0x03a0: bb4a 46b1 07de cc86 c5da dcc7 d5da e7fe
0x03b0: cec6 6ef6 f23e beb9 a321 34d0 f4d6 0354
0x03c0: db80 140c 1512 d992 6554 a6a7 180f 1da0
0x03d0: dfe2 094e b215 60ee 4168 0fbf dfd4 f0d9
0x03e0: 1574 646b 417d 23bf 7f1b 6e27 9348 e343
0x03f0: 92a1 884d dc30 8398 570e e3bf 9b94 8322
0x0400: 57cf 8653 0716 37b5 87ec 3004 758b efcc
0x0410: 4eea bd03 8b6a 24da db54 8064 4956 6226
0x0420: 9495 fa18 da52 6376 fe95 02fd 5c2e b53a
0x0430: b88f cb44 4f17 bd85 c6b7 78e7 407f 4868
0x0440: f1ba 3b2b ada7
As you can see, highlighted, the length of the message is visible, which indicates 1024 bytes. After that, 9 bytes of the lower layer are there, but now they are no more readable, since they are encrypted. After that, we just have question marks: how long is the prefix? If we don't know that, we can locate any part of the message after that, so encryption and random encapsulation is doing its job (not to speak about RSA padding).
This message is encrypted with RSA, and padded adeguately. The following message will be setup by using AES encryption, so even that traffic will result unreadable, thus hiding both content and final destination of the information.
Let me repeat it again: do not use it in real environments, since it has been poorly tested and can crash in particular setup. Having a market-level utility for such a network is outside the scope of the article.
The most comfortable environment where to run the prototype is a couple of Linux machines, but even Virtual Machines are good enough (in fact, I'm using this second setup). Copy the code where you like more, then build the project by invoking the make
command where the sources are located. If everything went fine, you will end up having the M2 application compiled and linked in the same directory of the source files.
user@debian:/tmp/src$ make
gcc -g -Wall -I./include -o m2 crypt.c kstore.c logic.c main.c
net.c node.c netloc.c proto.c tunw.c -lpthread -lssl -lcrypto
Remember that, in case you will need unencrypted data traffic (for debugging purposes), you will need to specify the -DEBUG
flag during compile time.
Local Machine Testing
It is possible to use only one machine for experimentation, and perform the connection and authentication steps with local IP addresses. To perform such an operation, you can use the prepare.sh and kgen.sh scripts, which already setup the environment in order to use two M2 instances.
user@debian:/tmp/src$ ./prepare.sh
user@debian:/tmp/src$ ./kgen.sh
Will use address 127.0.0.1:9000
Private/public keys for 127.0.0.1 successfully generated...
Will use address 127.0.0.2:9000
Private/public keys for 127.0.0.2 successfully generated...
user@debian:/tmp/src$ ls -l
total 212
-rw-r--r-- 1 user user 3272 Oct 5 09:02 127.0.0.1.priv
-rw-r--r-- 1 user user 800 Oct 5 09:29 127.0.0.1.pub
-rw-r--r-- 1 user user 800 Oct 5 09:29 127.0.0.2.pub
-rwxr-xr-x 1 user user 4936 Oct 5 09:29 crypt.c
drwxrwxrwx 2 user user 4096 Oct 5 08:59 include
-rwxr-xr-x 1 user user 438 Oct 5 09:29 kgen.sh
-rwxr-xr-x 1 user user 5366 Oct 5 09:29 kstore.c
-rwxr-xr-x 1 user user 7077 Oct 5 09:29 logic.c
-rwxr-xr-x 1 user user 105240 Oct 5 08:59 m2
-rwxr-xr-x 1 user user 7715 Oct 5 09:29 main.c
-rwxr-xr-x 1 user user 416 Oct 5 09:29 Makefile
drwxr-xr-x 2 user user 4096 Oct 5 09:29 n1
drwxr-xr-x 2 user user 4096 Oct 5 09:29 n2
-rwxr-xr-x 1 user user 2801 Oct 5 09:29 net.c
-rwxr-xr-x 1 user user 2322 Oct 5 09:29 netloc.c
-rwxr-xr-x 1 user user 13724 Oct 5 09:29 node.c
-rwxr-xr-x 1 user user 14 Oct 5 09:29 nodes
-rwxr-xr-x 1 user user 200 Oct 5 09:29 prepare.sh
-rwxr-xr-x 1 user user 7112 Oct 5 09:29 proto.c
-rwxr-xr-x 1 user user 1771 Oct 5 09:29 tunw.c
user@debian:/tmp/src$
The node instances will be isolated in n1 and n2 directories: they both contains the other node RSA public key (for authentication purposes), their own RSA keys (private and public) and the properly formatted ./nodes file (the list of peers).
Open a console for n1
and another one for n2
, then run the application n1
with the command:
user@debian:/tmp/src/n1$ ./m2 --addr 127.0.0.1
Will use address 127.0.0.1:9000
RSA keys for 127.0.0.1 loaded into the key store
Private/public keys loaded
Nodes network initialized
Loading known nodes:
Node at 127.0.0.2:9000 discovered
Starting to operate on 127.0.0.1:9000...
Node 127.0.0.2:9000 thread started...
Same operation shall be done in the n2
console window, but this time, you need to invoke the instance in passive mode, or they both will start to authenticate and keepalive in a messy and not compatible way.
user@debian:/tmp/src/n2$ ./m2 --addr 127.0.0.2 --no-nodes
Will use address 127.0.0.2:9000
Will not load nodes files
RSA keys for 127.0.0.2 loaded into the key store
Private/public keys loaded
Nodes network initialized
Starting to operate on 127.0.0.2:9000...
Node 127.0.0.1:37830 thread started...
Msg=1, size=1024
Msg=2, size=128
Msg=2, size=96
Msg=2, size=128
Msg=2, size=128
Msg=2, size=128
[...]
Remote Machine Testing
Running M2 on different machines work in the same way as on the local one, since sockets provides abstraction on the connection mechanism. You will need two machines or Virtual Machines which are connected between each other (reachable, for example, using ping).
Once you transferred the project (or the binary file) on both the machines, you need first to configure the network and make sure that the two instances can communicate between each other. I do this by configuring on the connected interfaces an IP address within the same network, like, on node 1:
root@debian:/tmp/src# ifconfig eth1 192.168.1.1
root@debian:/tmp/src# ifconfig eth1
eth1 Link encap:Ethernet HWaddr 08:00:27:dc:b0:aa
inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fedc:b0aa/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:5 errors:0 dropped:0 overruns:0 frame:0
TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:414 (414.0 B) TX bytes:1140 (1.1 KiB)
And for the node 2:
root@debian:/tmp/src# ifconfig eth1 192.168.1.2
root@debian:/tmp/src# ifconfig eth1
eth1 Link encap:Ethernet HWaddr 08:00:27:fa:db:e1
inet addr:192.168.1.2 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fefa:dbe1/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:5 errors:0 dropped:0 overruns:0 frame:0
TX packets:13 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:414 (414.0 B) TX bytes:1062 (1.0 KiB)
root@debian:/tmp/src# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.481 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=0.602 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=0.580 ms
^C
--- 192.168.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.481/0.554/0.602/0.056 ms
If the ping succeeded, then the machine are connected correctly.
The next step then is compile and generate the machines private/public keys. This is node by invoking make and running the instance with the addr
and kgen
flags, like follows:
root@debian:/tmp/src# make
gcc -g -Wall -I./include -o m2 crypt.c kstore.c logic.c main.c
net.c node.c netloc.c proto.c tunw.c -lpthread -lssl -lcrypto
root@debian:/tmp/src# ./m2 --addr 192.168.1.1 --kgen
Will use address 192.168.1.1:9000
Private/public keys for 192.168.1.1 successfully generated...
Repeat the same operation with the second machine, by changing all the references to 192.168.1.1 with the address used within the machine (in this case 192.168.1.2).
Once you did it, you need to edit the nodes files in the instance 1, to trigger the connection attempt to the second instance.
root@debian:/tmp/src# echo "192.168.1.2 9000" > nodes
root@debian:/tmp/src# cat nodes
192.168.1.2 9000
Last step for the test setup is exchange the public key between the keys, or otherwise they will have no way to authenticate the remote connection. On node 1 is done as follows:
root@debian:/tmp/src# scp ./192.168.1.1.pub user@192.168.1.2:/tmp/src
user@192.168.1.2's password:
192.168.1.1.pub 100% 800 0.8KB/s 00:00
While on node 2, you perform a similar operation:
root@debian:/tmp/src# scp ./192.168.1.2.pub user@192.168.1.1:/tmp/src
user@192.168.1.1's password:
192.168.1.2.pub 100% 800 0.8KB/s 00:00
If everything is correctly in place, now you can start passive operations on node 2 by issuing the following command:
root@debian:/tmp/src# ./m2 --addr 192.168.1.2 --no-nodes
Will use address 192.168.1.2:9000
Will not load nodes files
RSA keys for 192.168.1.2 loaded into the key store
Private/public keys loaded
Nodes network initialized
Starting to operate on 192.168.1.2:9000...
And you can start node 1 without the passive flag, so it will try to connect to the remote host. In addition to this, this time you will also need to open a communication flow (logic layer session) to that node, because you desire to exchange encrypted and Anonymous message with it. The following command will do the trick:
root@debian:/tmp/src# ./m2 --addr 192.168.1.1 --dest 192.168.1.2:9000
Will use address 192.168.1.1:9000
Destination 192.168.1.2:9000 acquired at 0
RSA keys for 192.168.1.1 loaded into the key store
Private/public keys loaded
Nodes network initialized
Loading known nodes:
Node at 192.168.1.2:9000 discovered
Starting to operate on 192.168.1.1:9000...
Node 192.168.1.2:9000 thread started...
(Re)Connected to 192.168.1.2:9000
Msg=1, size=1024
Msg=3, size=192
Processing end-to-end Session message, size=70
Msg=2, size=128
Msg=2, size=96
Msg=2, size=128
Now, if the operations performed successfully, M2 should have created a TUN interface within your system. Such interface is not active by default, and you will need to assign a proper IP address to it. Such operation is done, on the first machine, by issuing the following commands:
root@debian:/home/user# ifconfig tun0
tun0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
POINTOPOINT NOARP MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:500
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
root@debian:/home/user# ifconfig tun0 192.168.2.1
root@debian:/home/user# ifconfig tun0
tun0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:192.168.2.1 P-t-P:192.168.2.1 Mask:255.255.255.255
inet6 addr: fe80::ff02:be:a0ed:cebc/64 Scope:Link
UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:5 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:500
RX bytes:0 (0.0 B) TX bytes:240 (240.0 B)
And the same operation must be done on the second machine. At the moment, you assign an IP address to this interface, you will start to see in the M2 console that logic-level messages will be generated: they are the packets which carries the encrypted data (which travels from TUN to TUN interface).
Msg=2, size=128
Msg=3, size=224
Processing logic routing message, size=91
Msg=3, size=224
Processing logic routing message, size=91
Msg=2, size=96
Msg=2, size=128
Now that everything is in place, you need to instruct the kernel about the new network, otherwise it has no idea of how to reach the desired destination, and this is done by adding new entry to the routing table (on both the machines):
root@debian:/home/user# route add -net 192.168.2.0/24 dev tun0
The last step now is using the interfaces, and this can be done by performing ping test or similar:
root@debian:/home/user# ping 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=11.3 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64 time=24.7 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=64 time=27.5 ms
64 bytes from 192.168.2.1: icmp_seq=4 ttl=64 time=27.5 ms
64 bytes from 192.168.2.1: icmp_seq=5 ttl=64 time=23.6 ms
64 bytes from 192.168.2.1: icmp_seq=6 ttl=64 time=30.8 ms
64 bytes from 192.168.2.1: icmp_seq=7 ttl=64 time=27.6 ms
^C
--- 192.168.2.1 ping statistics ---
7 packets transmitted, 7 received, 0% packet loss, time 6008ms
rtt min/avg/max/mdev = 11.365/24.776/30.873/5.883 ms
As you can see, now the delay has increased, and this is thanks to:
- Encryption: which must be performed twice per packet (one at logic layer, the other on node layer).
- Random routing: every packet is created with credit 4, so it will bound 4 times around this two nodes network before being actually processed by the end-point.
- Debugging traces: every time a packet is generated, sent and received M2 show something on the console. Generating such trace is really time consuming, since requires interactiv with slower I/O devices.
Anonymous networks is a great and powerful tool, but as all tools can be used in a good and bad way. It is up to the user to operate with consciousness, since not only does it protect you from being persecuted by authoritary governments, but it also blocks police and agencies to locate and stop criminality. Almost no one is interested in you if you download film or audio tracks from the internet, but if you start to move/sell illegal contents like weapons, drugs and child pornography, then problems get real.
I want to add/remarks some considerations now that I went through the surface of Anonymous networks:
- Performances can be a problem, and end-to-end encryption can be enough (since the real data is encrypted). Encryption at the bottom-most level can be skipped if requirements on speed are not met (excluding the initial authentication and end-to-end session establishment of course, that can be setup using RSA keys). When this happens, anyone which desire to sniff your traffic still does not have access to the source of the message, but can use the session ID bound to the destination to approximate the source: this can be partially solved by using some random sequence setup during end-to-end session establishment.
- As I said, the routing part is not-existent, but a more complex (short-path like) algorithm can be easily introduced in this architecture to perform more complex choices rather than the random delivery. Take in account that easily predictable pick, if not properly handled, can disrupt Anonymity.
- For data processing, probably it is better to add another layer on the top of the logic one, and leave the routing where it is. This will allows proper abstraction between network management (sessions creation, routing information exchange, etc.) and the actual user packets exchange.
History
- 6th October, 2017: Initial release of the article and code
- 15th October, 2017: Fixed up some misunderstanding in TOR architecture, thanks Lukasz W for the feedback