这些天看了一下 croc 库的实现。这是一个用来传输文件的工具,用处和 sftp, sz/rz 这些东西类似。但是不同之处在于这个工具文件的收发需要依赖于外部的一个 relay server。

  1. sender 先与 relay server 建立连接,生成一个随机的 secret,注册房间信息 room。
  2. sender 通过某种方式将 secret 发送给 receiver。(邮件,微信,????)
  3. receiver 带上这个 secret 连接 relay server。找到对应的房间信息。
  4. relay Server 将这两个 TCP 连接通过管道 pipe 连接,让他们相互通信。
  5. sender 将文件发送给 receiver。

从 croc 库中学到一个新名词 Pake

当然这种信息传递方式没有加密的话是非常危险的,尤其是在还有一个 relay server 存在的情况下。

  1. 在 sender 与 relay server 建立连接注册 room 之前。
  2. 在 receiver 与 relay server 建立连接获取 romm 之前。
  3. sender 和 receiver 连接 pipe 之后,传递文件数据之前。

在上述 3 个时间点连接的双方会使用一种叫做 Pake (Password-Based Authenticated Key Establishment Protocol) 的协议协商加解密的密钥。中文叫做 口令认证密钥交换协议

Pake

具体的原理可以参看 一个酷炫却不普及的协议——PAKE,我对于这种数学算法比较头疼,就不解释了。

下面写个代码例子看一下。

// 这既是公钥。必须双方一开始约定好。
weakPassword := "foo-bar"

// P 为 sender,Q 为 receiver。
// P 和 Q 都使用同样的 weakPassword 初始化。
P, err := pake.InitCurve([]byte(weakPassword), 0, "siec")
if err != nil {
    panic(err)
}

Q, err := pake.InitCurve([]byte(weakPassword), 1, "siec")
if err != nil {
    panic(err)
}

// P -> Q
// P 将自身信息发送给 Q,所以 Q 更新。
if err := Q.Update(P.Bytes()); err != nil {
    panic(err)
}
// Q -> P
// Q 再将自身信息发送给 P,P 更新。
if err := P.Update(Q.Bytes()); err != nil {
    panic(err)
}

// 得到双方的 sessionKey,即协商密钥。
KeyP, err := P.SessionKey()
if err != nil {
    panic(err)
}
KeyQ, err := Q.SessionKey()
if err != nil {
    panic(err)
}

// 可以发现是相同的。
fmt.Printf("Key of P: %s\n", hex.EncodeToString(KeyP))
fmt.Printf("Key of Q: %s\n", hex.EncodeToString(KeyQ))

得到打印:

15:26 test ➜  go run main.go
Key of P: cebe3dbfc2afb1579e4649726e8ce352de3dc59ce5ca7ab31bebaf5b0aa36a9c
Key of Q: cebe3dbfc2afb1579e4649726e8ce352de3dc59ce5ca7ab31bebaf5b0aa36a9c

你可能得到的打印与我不一样,很正常。每次打印都是有随机数影响的,都是不一样的。

这样双方就能用这个密钥进行对称加密通信传输数据了。中间人只能截获到中间数据,无法推算出最后的密钥。即使知道了一开始的公钥,也没用。

如果双方一开始使用的 weakPassword 就不相同的话。在 P.Update(Q.Bytes()) 处就会报错了。

相关文章: