设计和开发匿名网络





5.00/5 (11投票s)
设计和开发类似 TOR 的协议,以保护您的通信安全并保护您的身份
引言
网络互联中的加密允许两个或多个实体在隐藏信息内容的同时交换信息。这意味着任何对“对话”感兴趣的人都无法提取信息,除非他/她没有必要的手段(密钥)将消息解密回原始形式。加密解决了信息安全(保密性)问题,但不能提供匿名性:这意味着你总是知道爱丽丝在和鲍勃通信,即使你不知道他们在说什么。
匿名网络允许以安全和匿名的方式交换信息。我想指出的是,这通常对网络“外部”的观察者是成立的,这意味着如果有人嗅探两个节点(PC、服务器或其他)之间流动的流量,他/她无法分辨消息来自哪里,也无法分辨消息去往何处。
但是如果匿名网络本身被攻破了呢?一个或多个转发节点(传输加密数据)可能落入黑客或国家机构手中,他们希望打破匿名性并定位流量的来源和目的地。
我在这里将提出的,是一个加密匿名网络的原型,它试图解决这样的问题。请注意,所提供的应用程序和想法只是一个粗略的草稿,可以在逻辑和技术方面进行改进;我目前的目的不是为这种想法提供一个市场级别的实用工具,而只是为了展示这种想法是可行的。
背景
我将逐步解释这个匿名网络的设计和实现,并尽可能地保持所有概念的简单性。仅仅这项任务就需要大量的文字(如你所见,滚动条很长),因此在描述软件时,我将跳过一些关于所用技术的概念。
读者必须熟悉C编程语言及其所有基础知识(内存分配、指针、运算符等),并且应该对套接字编程和IP网络有一定的背景知识。项目中将使用密码学,因此需要理解一些加密/解密/认证概念。
该项目需要OpenSSL库libcrypto
,版本1.1.0或兼容版本;最终,你可以轻松地根据你的版本修改代码。
该软件是为Linux开发的,因此也需要对该操作系统有一定的背景知识。
目录
Tor,洋葱路由协议
Tor是一款提供匿名网络服务的流行应用程序。它通过以保密方式(加密)在网络成员之间中继流量,直到达到预先选择的“边界节点”并且流量离开网络(以明文状态,即未加密)。这是通过执行一系列步骤来在网络内部准备一个流来实现的,这些步骤是:
- 下载/更新网络成员列表(来自Dave,见图1)。
- 通过在此网络中为您的数据选择路径,通过在沿途节点中初始化一些会话密钥。在开始时,数据包用不同的加密密钥多次封装,这意味着在每个跳跃中,信息都使用不同的密钥解密。
- 沿着新创建的“流”发送数据。每个步骤都会消耗数据包的一部分(该跳的头部),直到到达出口点(边界节点),然后数据包的数据(用先前设置的边界节点会话密钥加密)被解密并以明文形式发送到互联网。
这里发生的是,爱丽丝联系节点1,并与它建立一个节点到节点的密钥。完成此阶段后,另一个会话密钥将与节点交换,并用作实际要交换数据的解密密钥。实际上,节点到节点的密钥以后可以用于其他流,而会话密钥将对该流保持唯一。一旦完成第一步,爱丽丝将通过节点1联系节点2。这是必要的,这样节点2就不会意识到是爱丽丝在建立路由,并且这种机制对于维护匿名性至关重要。然后操作重复:为节点1到节点2的通信建立一个节点到节点会话(可以用于多个流),并在爱丽丝(由节点1掩盖)和节点2之间交换一个用于数据解密的会话密钥。
现在,会话密钥在隧道创建过程中逐段交换;在每一步中,接受它的节点只知道前一个节点和流中的下一个节点是谁,但无法看到整个电路的完整视图。即使节点2不完全知道来源是谁,它仍然能够将一个流的来源与另一个流区分开来,这意味着它可以对各种流量进行分类。
对Tor网络的常见攻击是在边界节点(图1,节点3)嗅探流量,这意味着你知道经过加密的信息是什么(保密性丧失)。如果边界节点(或构成网络的转发节点)也被攻破/入侵,那么你也可以导致匿名性丧失,因为你将能够查看流量到达节点时的内容。请记住,用于解密流量的会话密钥交换唯一地标识了沿途的所有流,即使你没有确切的来源。
Tor在保护那些希望在人权被剥夺、军事/政治国家否定言论自由的国家进行通信的人民方面已经做得非常出色,但该网络似乎无法自保。当然,这是一种特殊情况和一种特殊攻击;对这种网络的监控只有在大量节点被攻击或被攻击者引入时才有可能。如果一个安全机构开始向Tor注入“脏节点”(完全合法,因为这种网络是由志愿者组成的),那么它将增加发现消息原始发送者的可能性。如果(更糟糕的是)该机构还控制着大量边界节点,那么它就可以同时恢复来源和交换的消息。
匿名网络的设计
如前所述,匿名网络需要两个先决条件才能工作:机密性和匿名性。
我们如何才能在爱丽丝和鲍勃之间的信息交换中同时保持这两者呢?
让我从以下几点开始讨论,假设它们是真实的:
- 通信的爱丽丝和鲍勃(终端接收者)未被黑客入侵/泄露。这很重要,否则无论你采取什么加密或技巧,流量甚至在离开应用程序内存区域之前就可能被嗅探。
- 爱丽丝可以识别鲍勃,反之亦然。爱丽丝通过受信任的第三方以受信任的方式获取了识别鲍勃的证书/密钥。如果有人成功地伪造鲍勃和爱丽丝的身份(通过窃取他们的私钥)进行中间人攻击,那么……你就无能为力了。
在以下匿名网络(我们称之为M2,作为原型名称)中,爱丽丝和鲍勃之间交换的数据是端到端加密的,密钥只有爱丽丝和鲍勃知道。爱丽丝和鲍勃有责任决定在收到这些数据后如何处理(以明文形式中继到互联网)。没有任何中继节点会知道这些密钥,因此它们将无法在数据在网络中移动时对其进行解码。
这样,只要爱丽丝和鲍勃不泄露信息,保密性就能得到维护。
现在,为了实现匿名性,我们必须将消息/数据包的来源隐藏在中继网络本身之外。目的地是强制性的,否则消息无法正确分派给鲍勃(以及回复给爱丽丝),但来源根本不是必需的。在会话密钥交换阶段,爱丽丝和鲍勃选择密钥和会话ID,该ID将用于在消息交换期间识别正确的加密/解密密钥。请注意,此ID不是固定的(正如源地址实际上是固定的),并且爱丽丝/鲍勃可以通过重新启动会话密钥交换阶段来随意替换它。
此外,网络内的消息交换是通过在节点之间的每个链接上使用不同的密钥来完成的。当通信开始时,一个节点连接到另一个节点,它执行一种握手程序,其中选择并交换一个公共密钥,以便执行高效的加密/解密(使用AES)。此加密密钥仅对该链接有效,如果同一节点连接到另一个节点,则会再次触发该程序并建立另一个密钥。所有在这两个特定节点之间流动的流量都将使用该密钥进行,该密钥可以随时由通道的两个成员之一续订。
在M2网络中,我也不想用特定的任务来区分节点;任何节点都可以是边界节点或中继节点。由节点决定通信的开始和结束位置,以及是否将此类流量传输到互联网。这不会为攻击者创建特殊目标,因为攻击者必须考虑每个节点,而不仅仅是这个私有网络中的特定节点集。最终,爱丽丝和鲍勃将交换数据(用于他们自己的通信)并中继其他对话的数据(不知道这些数据来自哪里)。通过这样做,你可以确保从爱丽丝流出的流量不仅仅是爱丽丝创建的流量,嗅探器无法区分这两种类型(爱丽丝创建的或爱丽丝中继的)。
让我们看看流量流经节点时理想情况下会发生什么。
从M2外部看,没有什么特别的事情发生;网络流量经过加密,如果付出巨大努力解密隐藏信息,也无法读取。如果解密成功,也只会发现部分信息,因为真实数据再次使用另一个不同的密钥隐藏。唯一可见的部分是外部IP/TCP头部,它提供传输控制和前一跳(可能是中继跳,因此不是原始来源)以及下一跳(可能再次是中继跳,因此不是原始目的地)的位置。
这里的区别在于中继节点本身如何感知数据包?请记住,在这个网络中,没有与原始源建立会话来解密信息,解密阶段是通过使用与直接邻居(直接与我连接的节点)连接过程中交换的密钥来完成的。那么,当信息被接收并加密时,到达的数据包中仍然可见的只是另一个包含数据包目的地和标识符的头部。其他一些元素可能存在,但它们与确定源/目的地无关。
拥有这些信息的节点现在可以遵循其策略和路由策略,因为源没有严格指定网络中每个节点必须遵循的路由。通过适应实际网络拥塞,遵循期望的流量管理策略,必须中继信息的节点在决定做什么时是完全独立的。它可以用来做出此类决定的唯一信息是目的地名称和一个无意义的ID(对他来说无意义,因为它可以在数据包之间改变)。
我还要指出,这里不需要中央权威(参见图1中戴夫交换活动对等体列表的角色)。爱丽丝可以自由选择并决定谁值得信任来中继她的流量,并且可以随时添加/删除此列表中的元素。攻击戴夫对这个网络没有影响,因为戴夫不是网络的一部分,不能向我们提供一个被攻击节点的列表。
设计的网络在往返时间方面不会有良好的性能,因为数据在网络中流动时会多次加密和解密。实际上,性能并不是我在这里的关注点,因为设计更好的应用程序(这里提出的是原型)和对网络行为的调整可以改善流量延迟。这里的主要目标是保护匿名性,所以目前我愿意牺牲一些性能作为代价。请记住,您不必匿名化所有流量,只需匿名化其中一部分;浏览仍然可以正常进行,而使用端口/地址上的聊天系统必须受到保护;明智地在您的PC中设置路由规则允许同时在线两种选项。
M2原型
我想强调的第一个重要点是,该应用程序是**在空闲时间编写的原型**,因此尚未准备好在商业/真实环境中使用。如果我能投入更多时间,它会更完善。无论如何,源代码已附在文章中,您只需获取并根据需要进行修改。
Bootstrap
当您运行M2时,它将像一个普通的控制台应用程序一样运行,并开始扫描命令行以获取命令。该应用程序有一个默认行为,即使用地址127.0.0.1,因为本地环路通常存在于Linux发行版中。这可以通过指定`--addr
如果没有其他选项,在初始化阶段,M2会寻找一个节点文件,并列出“受信任对等体”的地址和端口。通过修改此文件并插入更多行,您可以增加与您连接的对等体数量,以及您到达目的地可能采取的路径。如果您决定将应用程序置于被动模式,即它只接受传入连接,则可以将`--no-nodes`选项传递给命令行。这样,M2将只保持静默并接受与其他节点的传入连接。
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");
/* Line by line file scan.
* The file has a dummy grammar, which is just "address port".
*/
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);
/* This is part of our stable dictionary */
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);
}
从前面的代码片段中,您可以推断出应用程序的几个要点
- M2维护一个“节点存储”,这是一个逻辑上组织的已知节点集合。这些节点将构成要使用的受信任网络。
- M2维护一个“密钥存储”,它聚合了应用程序使用的密钥。它包含用于验证节点的RSA公钥(如果您未将受信任节点的公钥放入M2文件夹,则不会与它们建立连接)、用于节点到节点连接的AES会话密钥以及用于两个远端节点之间通信的会话密钥。最后一组包含密钥和会话ID,这些密钥和会话ID将用于解密发送给我们的数据包(而不是中继)。
- 地址通过**netloc**结构进行抽象,这允许我们在单个元素中使用IPv4和IPv6地址。在内部,M2将使用它而不是使用原始地址。这是一个有用的抽象,以便以后将其扩展到多种技术,例如原始以太网。
typedef struct network_location {
int type; /* AF_INET or AF_INET6 */
union {
struct sockaddr_in in4; /* L3 address + L4 port address */
struct sockaddr_in6 in6; /* L3 address + L4 port address */
};
} netloc;
- 节点描述中存在的“Fix”标志触发重新连接机制。如果一个节点是被动的(或者其节点列表中不存在该对等节点),它将不会尝试重新连接到该远程端点:尝试再次连接到我们的任务由原始请求者负责。
- 将为节点加载**RSA公钥**。这在身份验证阶段使用,因为第一个消息将使用此密钥加密。加载失败将跳过此连接,并且所选插槽将用于节点文件中的下一个元素。不要添加您不信任的节点的公钥。
如果一切顺利,应用程序将启动一个线程来服务此连接。断开连接后,线程不会终止,并将持续尝试重新连接,直到您关闭M2。
与邻居通信
M2中线程行为的核心在node.c文件中:`node_loop`过程将持续循环,直到连接关闭(且节点未被“修复”),或者直到用户使用Ctrl-C信号命令应用程序停止。此线程初始化仅由该线程使用的结构和缓冲区,并且在某些操作触发之前不会尝试锁定公共资源。通过这种方法,我们可以将并发性降到最低,但请记住,这是一个原型,可以用更好的方式编写。
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) {
[...]
}
如前所述,如果节点被标记为“固定”(从节点文件中读取的条目),那么线程不会死亡,并且在套接字因错误关闭时会尝试重新连接到远程主机。如果发生此类事件,线程会重新启动连接,并尝试为新套接字上的通信设置会话密钥。默认情况下,所有在此处流动的流量都将使用256位密钥的AES加密进行加密/解密。密钥和输入向量将使用OpenSSL随机例程随机选择。
/* No communication on place; re-establish... */
if(n->sockfd <= 0) {
/* If we don't have to retry connection, terminate */
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;
}
}
一个重要的注意事项是,您始终要记住正确地为随机例程播种。这不是通过使用`srand(time(0))`来完成的,正如您总能在大量旧文章中找到的那样:它在密码学上是不安全的。相反,您应该使用时间本身的MD5哈希(或另一种单向函数)来播种。这在M2的初始化步骤中(在`main.c`中)完成。
time_t t = time(0);
[...]
/*
* Salt the random generator with proper value
*/
MD5_Init(&md5ctx);
MD5_Update(&md5ctx, &t, sizeof(time_t));
MD5_Final((unsigned char *)md5dig, &md5ctx);
RAND_poll();
RAND_seed(md5dig, 16);
由于M2使用TCP流,节点循环中接下来发生的是数据检索:线程读取前4个字节,其中包含后面数据的明文(未加密)长度,然后读取消息本身。在此收集过程中,如果没有读取到数据(在套接字上的读取操作中设置了`DONTWAIT`选项),则会触发**keep-alive**机制,并测试应用程序创建的虚拟设备(将在本文后面讨论)的I/O。这将检查套接字是否仍然打开并准备就绪,或者是否已被系统关闭。
最后,当整个消息收集完毕时,循环的最后几行将执行消息的解密,具体取决于链路密钥的状态。如果链路密钥已被撤销,线程将默认回退到RSA加密/解密(这非常消耗资源),以避免数据丢失。
分层方法
将应用程序组织成层次和模块总是对保持项目良好状态很有用。模块可以修改/交换而无需维护扭曲的依赖关系,并且层次允许减少从一个逻辑任务到另一个逻辑任务的复杂性。我在网络互联中学习了RINA中的分层,我想我永远不会忘记这种方法。
M2在通信方面分为两个不同的层,它们在通信周期中具有不同的域。下层执行节点到节点的数据交换,并提供基本的认证以信任远程节点并交换“链路密钥”。这一层不知道它将如何被使用,而上层则不关心工作如何执行,只要它正确完成即可。
这种方法允许应用程序分离不同的机制,这些机制可以在不干扰应用程序其他组件的情况下进行改进/更改。这意味着逻辑层可以更改协议格式和行为,而节点层无需意识到这一点(反之亦然)。保持这种组织对于处理代码日益增加的复杂性至关重要。
目前M2中**没有路由**,它需要直接连接到其网络中的所有节点。对这种架构的一个可能的改进是引入一个逻辑层消息,负责交换加密的路由表,从中可以到达数据包的最终目的地。
协议和消息
M2网络交换的消息位于proto.h头文件中,并且可以通过它们所属的层来区分。M2交换的“最低”消息是节点层消息。从置于受信任网络**外部**的实体来看,此类信息以以下格式传输
头部的一些字节(目前是四个)标识接下来要交换的消息的长度,而后面的信息将被加密,没有适当的密钥无法读取。一旦节点检索到消息,就会对收到的(完整)数据包应用解密。目前,这是通过一个简单的选择来完成的:如果已建立链路密钥,则使用AES加密,否则使用节点的RSA私钥对消息进行解码。
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
/* No session key? We are using RSA to exchange it */
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;
}
如前所述,节点总是尝试将交换和建立会话作为第一步操作,因此交换的第一个消息使用接收方公钥加密,接收方使用私钥解密(这保证只有他能够解密)。此信息中包含。
解密的节点层流量具有以下格式
头部包含有关其后数据有意义的信息,并标识消息前缀和后缀的大小。这两个随机大小的数组(包含随机数据)有助于隐藏数据包的真实尺寸,因为流量可以通过其大小轻松分类。哈希字段充当基本的输入验证工具,并有助于识别从节点可能接收到的随机流量中有效的数据(在您尝试解密之前,您无法区分随机数据和加密数据……并且您可以解密随机数据)。
/* Main header for protocols */
typedef struct proto_head {
uint8_t type;
uint32_t len;
uint16_t pre;
uint16_t post;
}__attribute__((packed)) p_head;
交换的消息有不同的类型,它们是
enum proto_types {
PROTO_INVALID = 0, /* Invalid type */
/*
* Lower layer protocols:
*/
PROTO_NODE_SE, /* Session key exchange */
PROTO_NODE_KA, /* Keep-alive */
PROTO_NODE_LO, /* Logic-layer traffic */
/*
* Logic layer protocols:
*/
PROTO_LOGIC_SE, /* End-to-end session exchange */
PROTO_LOGIC_RT, /* Routing data */
};
节点层消息执行链路认证(节点到邻居)和保活服务,检查连接是否仍然有效。这些机制在图8所示的状态机中进行了总结。会话建立通过交换和协商一组用于后续使用AES加密对数据进行加密/解密的密钥和IV来简单地工作。
逻辑层消息封装在逻辑层数据包中,其格式如下:
同样,一个头部标识了交换消息的类型。这是节点层消息的简化版本,因为许多服务已经由下层执行。会话建立与下层相同,区别在于现在协议中包含了ID的选择,该ID用于目的地选择正确的密钥来解密传入消息。
/* End-to-end session between source and destination */
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;
最后,爱丽丝和鲍勃之间交换的数据格式如下:
并在到达鲍勃之前由中间节点中继。在此事务期间,如前所述,中继节点无法识别流量的真实来源,因为在源和中间节点之间没有交换密钥。“路由数据”消息还有一个简单技巧,可以增加网络的随机性,即随机投递。这之所以有效,是因为没有路由,并且我假设各种节点之间存在一个完全连接的网络;一旦在网络中引入适当的路由,该机制当然可以改变。
/* Data routed into 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;
数据包的**credit**字段作为数据包的“生存时间”属性,只要它不为0,节点就会继续选择一个随机的下一个跳,然后将流量传递给它们。当credit耗尽时,数据包将被发送到实际目的地,该目的地应该拥有解密它所需的密钥。
以下随机路由策略在处理TCP流量时肯定存在一些问题,因为一个链路可能比另一个链路慢,并且数据包可能会开始乱序传递。将M2扩展以改变路由策略或提供必要的重新排序只是实现问题,并且由于分层架构,应该很容易引入它而不会弄乱其他组件。
M2的输入/输出
M2通过为每个建立的端到端会话创建一个虚拟接口来工作。这意味着您可以在此网络中移动任何可以通过/使用IP传输的流量。由于虚拟接口(TUN设备)可以与IP关联,或者您可以轻松修改路由规则以将一类流量重新路由到此类设备上。您可以实际决定哪种类型的信息必须加密和匿名传输,同时让您喜欢的流媒体或在线游戏应用程序在普通互联网上正常运行。
最终,从一个M2实例移动到另一个M2实例的流量是
嗅探M2流量
M2带有一个调试功能,该功能禁用应用于数据的任何加密/解密例程,同时保留所有其他功能。如果您使用`-DEBUG`标志编译项目,您将能够使用`Tcpdump`等应用程序嗅探流量并查看未加密的消息流。此功能当然很危险,不应集成到软件的发布版本中,因为它会破坏匿名性和机密性。
一旦与邻居对等节点建立连接,节点就会发出链路会话请求消息,以便配置一个通用、性能更好的AES加密/解密密钥。
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
在此数据包中突出显示的是消息的有趣部分,它从节点层头部开始,指示消息类型0x01,内部数据大小(0x2b7),随机前缀长度(0x1d)和随机后缀长度(0x10)。头部之后是前缀随机数据,然后是`proto_session_key`消息结构。
第一个字段是操作,它是一个请求(0x01),后面是32字节的AES密钥和32字节的IV。我用点分隔了此消息的三个字段,这在正常的`Tcpdump`输出中是不存在的。结构之后是使用发送方私钥加密的认证哈希,可以使用公钥解密;这对于确保识别会话请求的来源是必要的。最后,一个哈希执行基本数据验证,消息结束时,您可以找到随机后缀。
相同类型的消息,启用加密后,将作为以下跟踪被嗅探到
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
正如你所看到的,突出显示的是消息的长度,它表示1024字节。之后,下层的9个字节存在,但现在它们无法读取,因为它们已加密。之后,我们只有问号:前缀有多长?如果我们不知道这一点,我们就无法定位消息的任何部分,因此加密和随机封装正在发挥作用(更不用说RSA填充了)。
此消息使用RSA加密,并进行了适当填充。后续消息将使用AES加密设置,因此即使该流量也无法读取,从而隐藏了信息的内容和最终目的地。
使用原型
让我再说一遍:不要在真实环境中使用它,因为它经过的测试很少,并且在特定设置下可能会崩溃。为这样的网络提供市场级别的实用程序超出了本文的范围。
运行此原型最舒适的环境是几台Linux机器,但虚拟机也足够好(事实上,我正在使用第二种设置)。将代码复制到您喜欢的位置,然后通过在源文件所在目录中调用`make`命令来构建项目。如果一切顺利,您最终将会在源文件所在的同一目录中看到M2应用程序已编译并链接。
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
请记住,如果您需要未加密的数据流量(用于调试目的),则需要在编译时指定`-DEBUG`标志。
本地机器测试
可以只用一台机器进行实验,并使用本地IP地址执行连接和认证步骤。要执行此类操作,您可以使用*prepare.sh*和*kgen.sh*脚本,它们已经设置好环境以使用两个M2实例。
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$
节点实例将隔离在`n1`和`n2`目录中:它们都包含另一个节点的RSA公钥(用于身份验证)、它们自己的RSA密钥(私钥和公钥)以及格式正确的`./nodes`文件(对等体列表)。
打开一个用于`n1`的控制台,另一个用于`n2`,然后使用以下命令运行应用程序`n1`
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...
在`n2`控制台窗口中也应执行相同的操作,但这次,您需要以被动模式调用实例,否则它们都会以混乱且不兼容的方式开始认证和保持活动。
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
[...]
远程机器测试
在不同机器上运行M2与在本地机器上运行方式相同,因为套接字提供了连接机制的抽象。您需要两台彼此连接的机器或虚拟机(例如,可以使用ping访问)。
一旦您将项目(或二进制文件)传输到两台机器上,您首先需要配置网络,并确保这两个实例可以相互通信。我通过在连接的接口上配置同一网络中的IP地址来完成此操作,例如,在节点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)
对于节点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
如果ping成功,则机器连接正确。
下一步是编译并生成机器的私钥/公钥。这通过调用make并使用`addr`和`kgen`标志运行实例来完成,如下所示
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...
对第二台机器重复相同的操作,将所有对192.168.1.1的引用更改为机器中使用的地址(在本例中为192.168.1.2)。
完成此操作后,您需要在实例1中编辑**nodes**文件,以触发连接到第二个实例的尝试。
root@debian:/tmp/src# echo "192.168.1.2 9000" > nodes
root@debian:/tmp/src# cat nodes
192.168.1.2 9000
测试设置的最后一步是交换密钥之间的公钥,否则它们将无法认证远程连接。在节点1上,操作如下:
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
而在节点2上,您执行类似的操作
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
如果一切就绪,现在您可以通过发出以下命令在节点2上开始被动操作:
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...
您可以启动节点1而不带被动标志,这样它将尝试连接到远程主机。此外,这次您还需要打开一个通信流(逻辑层会话)到该节点,因为您希望与它交换加密和匿名消息。以下命令将完成此操作
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
现在,如果操作成功执行,M2应该已经在您的系统中创建了一个TUN接口。该接口默认情况下不活动,您需要为其分配一个适当的IP地址。此操作在第一台机器上通过发出以下命令完成:
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)
第二台机器也必须执行相同的操作。此时,一旦您为此接口分配了IP地址,您将开始在M2控制台中看到生成逻辑级别消息:它们是承载加密数据(在TUN接口之间传输)的数据包。
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
现在一切就绪,您需要向内核说明新网络,否则它不知道如何到达所需目的地,这通过在路由表中添加新条目来完成(在两台机器上):
root@debian:/home/user# route add -net 192.168.2.0/24 dev tun0
现在最后一步是使用接口,这可以通过执行ping测试或类似操作来完成
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
如你所见,现在延迟增加了,这要归功于:
- 加密:每个数据包必须执行两次(一次在逻辑层,另一次在节点层)。
- 随机路由:每个数据包都以信用值为4创建,因此它将在这两个节点网络中转发4次,然后才由端点实际处理。
- 调试跟踪:每当生成、发送和接收数据包时,M2都会在控制台上显示一些内容。生成此类跟踪非常耗时,因为它需要与较慢的I/O设备进行交互。
结论和最终思考
匿名网络是一个强大且有用的工具,但像所有工具一样,它既可以用于好的方面,也可以用于坏的方面。用户有责任自觉操作,因为它不仅可以保护您免受专制政府的迫害,还可以阻止警察和机构定位和制止犯罪活动。如果您从互联网下载电影或音轨,几乎没有人会关注您,但如果您开始移动/销售武器、毒品和儿童色情等非法内容,那么问题就真的来了。
现在我已经粗略介绍了匿名网络,我想补充/评论一些看法。
- 性能可能是一个问题,端到端加密可能就足够了(因为真实数据是加密的)。如果无法满足速度要求,可以跳过最底层加密(当然,不包括初始认证和端到端会话建立,这些可以使用RSA密钥进行设置)。当这种情况发生时,任何想要嗅探您的流量的人仍然无法访问消息的来源,但可以使用绑定到目的地的会话ID来近似来源:这可以通过在端到端会话建立期间设置一些随机序列来部分解决。
- 如前所述,路由部分是不存在的,但可以很容易地在这种架构中引入更复杂的(类似短路径的)算法,以执行比随机交付更复杂的选择。请记住,如果处理不当,易于预测的选择可能会破坏匿名性。
- 对于数据处理,最好在逻辑层之上再添加一层,并将路由保留在原处。这将允许网络管理(会话创建、路由信息交换等)与实际用户数据包交换之间进行适当的抽象。
历史
- 2017年10月6日:文章和代码首次发布
- 2017年10月15日:修正了TOR架构中的一些误解,感谢Lukasz W的反馈