返回首页
/post-69366e4d2eaa480716a28716

关于钱包加密签名验证

创建于 2025/12/8 06:21:01
Web3钱包

写在前面,其实分析这个业务之前做了大量理论查询和论证,以及参考了大量钱包作为对比,然后算是一篇总结性的理论吧。

除了前端的架构就是浏览器框架插件那块,单拎一个签名验证出来来分析下业务, 其实熟悉了也还行,就是刚开始的时候需要一些理解。

这是我们定好的通讯协议内容 密钥协商 X25519 对称加密 AES-256GCM

很简短对吧,X25519 需要俩端自己独立计算,由插件端计算完成后, 使用 AES-256GCM 协议加密传输到后端,后端相对的拿到加密串进行解密验证

业务完成。

上面是用最简短的人话说了上面的业务,只是业务刨除什么浏览器框架那一套以及前端框架那些,这篇只暂时针对这个业务以及业务完成闭环逻辑。至于 X25519 和 AES-256GCM 这俩协议包要研究那算是另外的课题了。

好,回到整体业务, 前端插件需要实现的协议要求: 密钥协商:X25519(用于建立共享密钥) 对称加密:AES-256-GCM(用于加密实际消息)

翻译成现实应用就是: 用 X25519 生成一个 双方都能推导出的会话密钥(shared key), 再从这个 shared key 派生出一个 256-bit 对称密钥, 用 AES-256-GCM 加密所有通信。

其实细化来说, 当然是想到哪儿说哪儿 x25519 推导会话密钥, AES-256-GCM 进行加密,当然涉及到许多问题, 简单来说,在当前会话的所有消息都会采用上述加解密,当然会有一些性能问题,但是安全需要做这种端对端的加密,当然派生问题也不少其实。

比如会话密钥的使用时机

  1. 保存 AES-GCM session key(只保存在内存)
  2. 所有请求 → 用 AES-GCM 加密
  3. 所有响应 ← 用 AES-GCM 解密 会话流程解决的是: 身份认证 加密通信 数据完整性 中间代理不可信 防止 replay 防止伪造请求 防止内部系统泄露明文 会话建立只是“安全通道建立的第一步”。 建立好之后,你要“使用”这个通道来收发数据。

解决的主要问题还是当前会话session建立之后,我们一直使用这个会话,直到页面刷新或者时间过期当然这个都是业务层面商定的。

其实聊到主流钱包到底需不需要这种端对端通信加密的这个业务, 就举例walletconnect肯定是需要的, 因为它们的通信路径是 跨设备,例如: 网页 ↔ 手机钱包 手机 ↔ 桌面应用 DApp ↔ Relay Server ↔ Wallet 不同设备之间不共享内存 有可能经过第三方中继服务器 网络可被监听 中继服务器不可信

只要过三方中继器,那么就需要加密机制,私钥泄漏这个很严重的,而且也有第三方不可信的这么这个问题。分享的事共享密钥,第三方拿到没有对应的机制也没有办法进行加密解密,这个要配合双方私钥对应使用的。

而 metaMask 不做的理由是, 因为: MetaMask 是本地注入,不经过网络 网页本身在浏览器沙箱内 origin 校验足够做身份认证 没有跨设备通信,也不存在中间人攻击 所有 sensitive 操作会弹 UI(用户确认) 所以 MetaMask 设计上不需要额外的 ECDH 加密层。 其实链上授权本身和这种加密通讯就是俩码事儿,根据业务选择可以做可以不做。

说白还是多签钱包的初步起始阶段,通讯内容最终使用AES-GCM-256进行一个加密流程。

┌────────────────────────────┐                     ┌─────────────────────────────┐
│        插件钱包 (Extension) │                     │      后端服务 / DApp Host    │
│  - curve25519-js (X25519)  │                     │  - X25519 / curve25519 实现  │
│  - AES-256-GCM             │                     │  - AES-256-GCM              │
└────────────┬───────────────┘                     └─────────────┬──────────────┘
             │                                                   │
             │ ① 生成本地密钥对(只在本地)                      │
             │──────────────────────────────────────────────────▶│
             │   pubWallet = generateKeyPair().public            │
             │   privWallet = generateKeyPair().private          │
             │                                                   │
             │ ② 发送自己的公钥 pubWallet                        │
             ├──────────────────────────────────────────────────▶│
             │      (例如通过 postMessage / HTTP / WebSocket)  │
             │                                                   │
             │                                                   │ ③ 生成对端密钥对
             │                                                   │   pubRemote, privRemote
             │                                                   │
             │                             ④ 返回 pubRemote      │
             │◀───────────────────────────────────────────────────┤
             │                                                   │
             │ ⑤ 双方本地各自计算 sharedKey(同一个值)          │
             │   插件:                                          │
             │     sharedKey = X25519(privWallet, pubRemote)     │
             │                                                   │
             │                                                   │  服务端:
             │                                                   │    sharedKey = X25519(privRemote, pubWallet)
             │                                                   │
             │ ⑥ 各自在本地做 KDF,派生对称密钥 aesKey           │
             │   aesKey = SHA-256(sharedKey)[0:32]               │
             │                                                   │
             │                                                   │  同样:
             │                                                   │  aesKey = SHA-256(sharedKey)[0:32]
             │                                                   │
             │ ⑦ 之后通信都走 AES-256-GCM                        │
             │   (插件这边)                                    │
             │   - 生成随机 iv                                  │
             │   - ciphertext = AES-GCM-Encrypt(aesKey, iv, msg) │
             │   - 发送 { iv, ciphertext }                      │
             ├──────────────────────────────────────────────────▶│
             │                                                   │
             │                                                   │ ⑧ 服务端用同一个 aesKey 解密:
             │                                                   │   msg = AES-GCM-Decrypt(aesKey, iv, ciphertext)
             │                                                   │
             │                                                   │ ⑨ 服务端回复时同样用 aesKey + AES-GCM 加密
             │◀───────────────────────────────────────────────────┤
             │                                                   │
             ▼                                                   ▼
   ❗ 私钥始终在插件本地                                  ❗ 私钥始终在服务端本地
   - privWallet 不发给任何网页                           - privRemote 不发给插件
   - 助记词 / 钱包私钥也只在插件内部                      - 只对等持有各自私钥

上面就是走的一个完整的流程图

游客模式