背景
加密是一个很有深度的话题,本文只是谈一下项目中的一点 POC 过程和思考。这里主要介绍一下 ECDH 的 Node.Js 实现,后续会再探讨一下 Golang,虽说语言是一部分,但是实际应用的过程中还是有非常多的坑要踩.
ECDH
了解 ECDH,首先要了解 ECC,也就是椭圆曲线加密。当然,本文主要讲实现,具体数学逻辑过程有兴趣的可以另外找相关文档,就当我是搬一下砖。
ECDH 一个秘钥磋商的过程,秘钥磋商,就是 Server & Client 双方可以在不通过任何请求传输秘钥的情况下,只根据对方的公钥,结合自身的私钥,磋商出共同的一个秘钥(AES key), 以此保证用于加密的秘钥的安全性。
结合公私钥加解密的思路最简而言之就是:
|
|
实践
首先用到的模块有:crypto, jsrsasign, fs . 因为没有研究透 jsrsasign.js 这个模块,所以偷懒结合了一下 Node 原生模块 crypto.js 来处理。
值得注意的是:
Node 相关的加密模块,crypto,jsrsasign 所操作的 key,基本都是需要传 PEM 格式的;ECDH API 是例外,用的是 ECPoint ,下文会提到。
- 我这里服务端只存了一对固定的 Private/Public Key,存的是 PEM 格式的。生成的方式可以使用在线工具,或者直接代码实现;
这里用的是 ‘secp256k1’ 曲线算法,算法列表可以通过 crypto.getCurves() 获取:
|
|
- 因为这次是跟 Android 做集成,其他客户端也一样,通常传递的 PublicKey 不会是完整的 PEM 格式,或是没有 —BEGIN PUBLIC KEY— / —END PUBLIC KEY— 的头尾结构,所以我们在接收到客户端的 KEY 时需要重新拼接首位结构,这里用两个很蠢的实现:
|
|
- 这一步是 ECDH 最重要的一步,根据 Node Crypto 的 API,官方提供了三个关键的API:
|
|
这里用到的 privateKey 是我们服务端的私钥,otherPublicKey 是客户端的公钥,而且 crypto 模块提供的 API 能接受的 privateKey 的格式,实际上是 ECPoint,而不是完整的 PEM 或者 X.509 标准之类的 KEY。
该结论可以从第二个 API - ecdh.generateKeypair(); 的输出得出,默认 generate 出来的公私钥都是核心 ECPoint 那一段,而不是标准的 X.509 或者 ASN.1
所以,我们需要从服务端的私钥 PEM 文件,提取出该 privateKey 的 ECPoint,和从客户端的公钥提取对应格式的 ECPoint。
|
|
最后是磋商,计算秘钥:
12345678910111213/*** Compute secret by public key PEM* @param {base64} otherPublicKeyPoint ecdh public key from other client/server* @returns {string | base64} secret key*/computeSecretByPEM(otherPublicKeyPEM) {const {pubKeyHex} = KEYUTIL.getKey(otherPublicKeyPEM);const ecdh = createECDH('secp256k1');ecdh.setPrivateKey(this.privateKeyPoint, 'base64');const secret = ecdh.computeSecret(hextob64(pubKeyHex), 'base64', 'base64');console.log('[secret]:', secret);return secret;};至于上面提到的,为什么我们要自己 setPrivateKey,是因为我们项目实际应用中还有签名和验签。所以需要使用我们自己的 privatekey、publicKey 来操作,而不用默认 Generate 出来的 Key.
下面是签名和验签:
|
|
总结
上述是 node 中的简单应用,我把我遇到的坑都一起总结在完整的代码里了,有些做法很粗糙,但是也是一个 POC 的结果,模块的 API 也没有很深入的研究,没有做到物尽其用,但是相信结论是相似的,如果有说错的地方,烦请纠正我,我也是刚学了一点点,不吝赐教。