EP04:不发送最终密钥,Alice 和 Bob 如何得到同一个共享秘密?
在前面的实验中,我们已经验证了普通 IP 通信存在的一些天然边界。
网络能够把数据包送到目的地,但这并不意味着通信内容天然安全,也不能仅凭 Source IP 证明真正的通信身份。
如果接下来希望保护通信内容,最容易想到的方法就是:
把数据加密。
但加密马上会带来一个新的问题。
假设 Alice 使用对称加密保护发送给 Bob 的数据,那么 Bob 必须拥有能够正确解密数据的密钥。
于是问题变成了:
Alice 和 Bob 如何获得共同的秘密?
最简单的方法似乎是 Alice 直接生成一把密钥,然后通过网络发送给 Bob。
但如果网络本身就是不可信的,那么攻击者既然能够截获后续通信,也可能在密钥传输阶段直接把这把密钥截获。
所以真正需要解决的问题并不是:
如何把最终密钥安全地发送过去?
而是:
能不能根本不发送最终 Shared Secret,却让 Alice 和 Bob 分别在本地得到同一个结果?
这就是本实验要验证的问题。
一、实验目标
本实验暂时不配置 IPSec,也不配置 IKE。
我们只验证密钥交换中最核心的一条逻辑:
Alice Bob
生成 Private A 生成 Private B
↓ ↓
生成 Public A 生成 Public B
│ │
└────── 交换 Public Key ─────┘
↓
Private A + Public B Private B + Public A
↓ ↓
Shared Secret A Shared Secret B
最终希望观察:
Shared Secret A
=
Shared Secret B
而通过网络交换的只有:
Alice Public Key
Bob Public Key
没有直接传输:
Alice Private Key
Bob Private Key
Shared Secret
实验最后再让 Kali 尝试只使用双方的 Public Key 完成同样的派生。
由此验证:
最终 Shared Secret 不需要作为一个完整的值直接经过网络传输。
二、实验环境
继续复用前面实验中的三台虚拟机。
| 角色 | 操作系统 | IP 地址 | 作用 |
|---|---|---|---|
| Alice | Rocky Linux | 192.168.106.130 | 生成密钥并计算 Shared Secret |
| Kali / Eve | Kali Linux | 192.168.106.131 | 中转并观察公开信息 |
| Bob | Rocky Linux | 192.168.106.132 | 生成密钥并计算 Shared Secret |
实验网络仍然为:
192.168.106.0/24
本实验使用:
OpenSSL
Python 3
curl
tcpdump
为了让 Public Key 的交换过程更加容易观察,Kali 暂时承担一个简单的:
Public Key Relay
Alice 和 Bob 的 Public Key 都会经过 Kali。
需要说明:
这里的 Relay 只是为了把“公开信息能够被不可信网络观察到”这件事展示得更加直观,并不是在模拟完整的 IKE 交换过程。
三、实验过程
整个实验可以概括为:
Alice Kali / Eve Bob
.130 .131 .132
│ │ │
│ 生成 Private A │ 生成 Private B │
│ 生成 Public A │ 生成 Public B │
│ │ │
│────── Public A ───────────>│<────────── Public B ─────│
│ │ │
│ │ tcpdump │
│ │ 可以看到双方 Public Key │
│ │ │
│<────── Public B ───────────│────────── Public A ─────>│
│ │ │
│ Private A │ Private B │
│ + │ + │
│ Public B │ Public A │
│ ↓ │ ↓ │
│ Shared Secret A │ Shared Secret B │
│ │ │
└────────────── Shared Secret A = B ───────────────────┘
而 Kali 最终只有:
Alice Public Key
Bob Public Key
没有任何一方的 Private Key。
四、确认实验网络
三台虚拟机启动后,先确认实验地址。
Alice:
ip -br addr
ip route get 192.168.106.132
应确认实验地址:
192.168.106.130
Bob:
ip -br addr
ip route get 192.168.106.130
确认:
192.168.106.132
Kali:
ip -br addr
ip route get 192.168.106.132
确认:
192.168.106.131
测试基本连通性。
Alice:
ping -c 3 192.168.106.131
ping -c 3 192.168.106.132
Bob:
ping -c 3 192.168.106.130
ping -c 3 192.168.106.131
再确认 Alice 和 Bob 已经安装 OpenSSL:
openssl version
到这里仍然只是完成实验环境准备。
五、Alice 生成 Private Key 和 Public Key
Alice 创建实验目录:
mkdir -p ~/ep04
cd ~/ep04
本实验使用 X25519。
首先生成 Alice Private Key:
openssl genpkey \
-algorithm X25519 \
-out alice_private.pem
然后从 Private Key 导出 Public Key:
openssl pkey \
-in alice_private.pem \
-pubout \
-out alice_public.pem
查看:
ls -l
应当得到:
alice_private.pem
alice_public.pem
查看 Public Key:
cat alice_public.pem
可以看到类似:
-----BEGIN PUBLIC KEY-----
......
-----END PUBLIC KEY-----
此时需要区分两个文件的用途:
alice_private.pem
↓
只保存在 Alice 本机
alice_public.pem
↓
允许通过网络公开交换
后面的实验都建立在这个区别上。
六、Bob 生成自己的密钥
Bob:
mkdir -p ~/ep04
cd ~/ep04
生成 Bob Private Key:
openssl genpkey \
-algorithm X25519 \
-out bob_private.pem
导出 Bob Public Key:
openssl pkey \
-in bob_private.pem \
-pubout \
-out bob_public.pem
检查:
ls -l
得到:
bob_private.pem
bob_public.pem
现在双方分别拥有:
Alice
alice_private.pem
alice_public.pem
以及:
Bob
bob_private.pem
bob_public.pem
到目前为止:
Alice 和 Bob 的 Private Key 都没有离开各自机器。
七、在 Kali 上建立 Public Key Relay
为了让双方交换 Public Key,同时方便观察交换内容,在 Kali 上建立一个简单 HTTP Relay。
创建目录:
mkdir -p ~/ep04
cd ~/ep04
创建:
nano relay.py
写入:
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
BASE = Path("relay")
BASE.mkdir(exist_ok=True)
ALLOWED = {
"/alice_public.pem": "alice_public.pem",
"/bob_public.pem": "bob_public.pem",
}
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path not in ALLOWED:
self.send_error(404)
return
length = int(self.headers.get("Content-Length", 0))
data = self.rfile.read(length)
path = BASE / ALLOWED[self.path]
path.write_bytes(data)
print(f"[+] Stored {path}")
self.send_response(201)
self.end_headers()
self.wfile.write(b"stored\n")
def do_GET(self):
if self.path not in ALLOWED:
self.send_error(404)
return
path = BASE / ALLOWED[self.path]
if not path.exists():
self.send_error(404)
return
data = path.read_bytes()
print(f"[+] Served {path}")
self.send_response(200)
self.send_header("Content-Type", "application/x-pem-file")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
server = HTTPServer(("0.0.0.0", 8000), Handler)
print("[+] EP04 public-key relay listening on 0.0.0.0:8000")
server.serve_forever()
启动:
python3 relay.py
正常情况下看到:
[+] EP04 public-key relay listening on 0.0.0.0:8000
现在 Kali 开始等待双方上传 Public Key。
八、Kali 同时观察公开交换的信息
另外打开一个 Kali 终端。
确认实验接口:
ip route get 192.168.106.130
假设当前实验接口仍然为:
eth1
开始抓包:
sudo tcpdump -i eth1 -nn -s0 -A 'tcp port 8000'
本实验故意通过普通 HTTP 交换 Public Key。
因此 tcpdump 可以直接观察这些公开信息。
这一点非常重要:
实验并不假设攻击者看不到 Public Key。
恰恰相反,我们就是让 Kali 能够完整看到它们。
九、Alice 和 Bob 上传自己的 Public Key
Alice:
cd ~/ep04
执行:
curl \
--data-binary @alice_public.pem \
http://192.168.106.131:8000/alice_public.pem
正常返回:
stored
Kali Relay:
[+] Stored relay/alice_public.pem
tcpdump 中还能直接看到:
-----BEGIN PUBLIC KEY-----
......
-----END PUBLIC KEY-----
Bob:
cd ~/ep04
执行:
curl \
--data-binary @bob_public.pem \
http://192.168.106.131:8000/bob_public.pem
Kali:
[+] Stored relay/bob_public.pem
查看:
ls -l ~/ep04/relay
此时 Kali 已经拥有:
alice_public.pem
bob_public.pem
也就是说:
双方所有经过网络交换的 Public Key,Eve 都可以完整获得。
十、Alice 和 Bob 获取对方的 Public Key
Alice 下载 Bob Public Key:
cd ~/ep04
curl \
http://192.168.106.131:8000/bob_public.pem \
-o bob_public.pem
现在 Alice 拥有:
alice_private.pem
alice_public.pem
bob_public.pem
Bob 下载 Alice Public Key:
cd ~/ep04
curl \
http://192.168.106.131:8000/alice_public.pem \
-o alice_public.pem
Bob 拥有:
bob_private.pem
bob_public.pem
alice_public.pem
此时三方状态非常清楚。
Alice:
Alice Private
Alice Public
Bob Public
Bob:
Bob Private
Bob Public
Alice Public
Kali:
Alice Public
Bob Public
区别就在于:
Alice 和 Bob 各自还拥有一份从未经过网络的 Private Key。
十一、Alice 本地派生 Shared Secret
Alice 使用:
Alice Private
+
Bob Public
进行计算。
执行:
openssl pkeyutl \
-derive \
-inkey alice_private.pem \
-peerkey bob_public.pem \
-out alice_shared.bin
检查:
ls -l alice_shared.bin
为了便于比较,计算 SHA-256:
sha256sum alice_shared.bin
例如:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx alice_shared.bin
注意:
alice_shared.bin是 Alice 本地派生得到的结果,它没有被发送给 Bob。
十二、Bob 本地派生 Shared Secret
Bob 使用:
Bob Private
+
Alice Public
执行:
openssl pkeyutl \
-derive \
-inkey bob_private.pem \
-peerkey alice_public.pem \
-out bob_shared.bin
然后:
sha256sum bob_shared.bin
比较 Alice 和 Bob 的结果。
实验正常时:
SHA256(alice_shared.bin)
=
SHA256(bob_shared.bin)
也就是:
Alice Shared Secret
=
Bob Shared Secret
这就是整个 EP04 最关键的实验现象。
十三、这个 Shared Secret 是从哪里来的?
现在回头看整个实验。
Alice 通过网络发送的是:
Alice Public Key
Bob 通过网络发送的是:
Bob Public Key
Kali 甚至把双方 Public Key 全部保存了下来。
但 Alice 的计算过程是:
Alice Private
+
Bob Public
↓
Shared Secret X
Bob 的计算过程则是:
Bob Private
+
Alice Public
↓
Shared Secret X
所以:
Alice Bob
Private A Private B
+ +
Public B Public A
↓ ↓
Shared Secret X Shared Secret X
双方得到了相同结果。
但:
Shared Secret X
从未作为一个完整值在网络中发送。
这就是本实验真正需要验证的东西。
十四、Kali 能不能只依靠两个 Public Key 完成同样的派生?
现在再看 Kali。
Kali 已经拥有:
alice_public.pem
bob_public.pem
进入:
cd ~/ep04/relay
尝试:
openssl pkeyutl \
-derive \
-inkey alice_public.pem \
-peerkey bob_public.pem \
-out eve_shared.bin
该操作无法按照 Alice 或 Bob 的方式完成派生。
因为 Kali 缺少通信双方各自在本地保留的 Private Key。
三方关系可以概括为:
Alice
Alice Private
+
Bob Public
↓
Shared Secret X
Bob
Bob Private
+
Alice Public
↓
Shared Secret X
而 Kali 只有:
Alice Public
+
Bob Public
↓
无法按同样方式得到 Shared Secret X
这里要注意实验边界:
本步骤展示的是:Kali 只有双方 Public Key 时,无法通过同样的 OpenSSL derive 操作得到通信双方的 Shared Secret。
实验并不是试图用一条命令完整证明 X25519 的全部密码学安全性质。
十五、关键实验结果
整个 EP04 最值得保留的证据其实只有三组。
第一组:Public Key 可以公开交换
Kali 的 tcpdump 可以直接看到:
-----BEGIN PUBLIC KEY-----
......
-----END PUBLIC KEY-----
而 Relay 中能够保存:
alice_public.pem
bob_public.pem
说明:
Eve 可以获得双方公开交换的信息。
第二组:Alice 和 Bob 本地得到相同 Shared Secret
Alice:
sha256sum alice_shared.bin
Bob:
sha256sum bob_shared.bin
最终 Hash 一致:
Shared Secret A
=
Shared Secret B
这是本实验最重要的证据。
第三组:Shared Secret 没有通过网络传输
网络中交换的是:
Alice Public
Bob Public
而 Shared Secret 分别在两端通过:
Private A + Public B
和:
Private B + Public A
本地计算产生。
因此:
Alice 和 Bob 得到同一个秘密,并不依赖于把最终秘密本身发送给对方。
十六、实验结论
回顾整个实验:
Alice Bob
生成 Private A 生成 Private B
↓ ↓
生成 Public A 生成 Public B
│ │
└──── Public Key 交换 ──────┘
↓
Private A + Public B Private B + Public A
↓ ↓
Shared Secret X Shared Secret X
通过网络能够观察到:
Alice Public Key
Bob Public Key
但不会直接出现:
Alice Private Key
Bob Private Key
Shared Secret X
因此,本实验验证了:
双方可以只交换公开信息,而不直接发送最终 Shared Secret,并分别在本地得到相同的共享秘密。
所以密钥交换真正解决的问题,不是:
怎样把最终密钥安全地发送出去?
而是:
让最终共同秘密根本不需要直接经过网络。
十七、但问题还没有全部解决
做到这里,看起来 Alice 和 Bob 已经拥有相同的 Shared Secret。
但还存在一个非常重要的问题。
Alice 下载的是:
bob_public.pem
于是她自然会认为:
这是 Bob 的 Public Key。
但到目前为止,没有任何机制证明:
这个 Public Key 真的属于 Bob。
Bob 也面临完全相同的问题。
本实验中的 Kali 非常老实:
Alice 上传什么
↓
Kali 原样保存
Bob 请求什么
↓
Kali 原样返回
但如果 Kali 不再老老实实中转呢?
例如:
Alice 请求 Bob Public
↓
Kali 拦截
↓
返回 Eve Public
Alice 仍然能够执行密钥派生。
Bob 同样如此。
问题只在于:
他们最终建立的 Shared Secret,还是彼此之间的吗?
于是 EP04 又留下了一个新的问题:
能够建立共同秘密,是否就代表我们已经确认了对方的真实身份?
答案将在下一实验中验证: