跳转到内容

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 地址作用
AliceRocky Linux192.168.106.130生成密钥并计算 Shared Secret
Kali / EveKali Linux192.168.106.131中转并观察公开信息
BobRocky Linux192.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 又留下了一个新的问题:

能够建立共同秘密,是否就代表我们已经确认了对方的真实身份?

答案将在下一实验中验证:

EP05:有了共同秘密,对面就一定可信吗?