mix-encryption:前后端通信混合加密实现

269 阅读7分钟

“完犊子了,我们客户的敏感数据被黑客拦截获取了!” 项目经理在那咆哮道。

“咋了,咋了,怎么回事儿啊?!”我正在刷掘金学习(摸鱼)呢,一吓就被吓了一跳。

项目经理:“我在测试那儿看到他的电脑上,能获取到我们小程序请求里面的数据啊,这太危险了,要是被黑客拿到了我们不完了啊”

我:“啊,那没事儿,测试用抓包工具测试呢,别人拿不到”

项目经理:”不行!测试能抓包,就说明别人也能拿到,你得给我把数据加密,这样别人抓包了也没用“

加密算法

开个玩笑,接下来进入正题。

我们为什么需要对数据加密?在有些保密要求比较高得场景中,比如金融场景,对于数据的保密要求是非常非常高的。通常会对用户的身份证信息、银行卡信息、电话信息等进行脱敏处理。但是第一次获取数据,或者上传数据时,由于要保存信息,这时总是明文的,我们就需要对数据进行加密。

加密算法分 对称加密 和 非对称加密,其中对称加密算法的加密与解密 密钥相同,非对称加密算法的加密密钥与解密 密钥不同,此外,还有一类 不需要密钥 的 散列算法

对称加密

  • 数据加密过程:在对称加密算法中,数据发送方 将 明文 (原始数据) 和 加密密钥 一起经过特殊 加密处理,生成复杂的 加密密文 进行发送。
  • 数据解密过程:数据接收方 收到密文后,若想读取原数据,则需要使用 加密使用的密钥 及相同算法的 逆算法 对加密的密文进行解密,才能使其恢复成 可读明文
  • 加密算法一般效率比较高,适合加密复杂数据

在这种加密算法中,加密的算法必须是同一种,密钥也必须是同一个。而这也就有一个问题:密钥如果用配置,那就需要写死在客户端和服务器端,如果通过接口从服务器端下发,则也有可能被拦截,很明显,这样并不安全。

非对称加密

  • 如果使用 公钥 对数据 进行加密,只有用对应的 私钥 才能 进行解密
  • 如果使用 私钥 对数据 进行加密,只有用对应的 公钥 才能 进行解密
  • 加密算法一般比较消耗性能,适合加密简单数据

服务器端生成一对密钥并将其中的一把作为 公钥 向客户端发送,得到该公钥的客户端使用该密钥对机密信息进行加密后再发送给服务器端,服务器端再使用自己保存的另一把 私钥,对加密后的信息进行解密。反过来,服务器端给客户端发送加密信息也是一个道理。同样的,钥匙也有可能被人拿到,也不够安全。

签名加密

常见的签名加密算法:MD5SHA1RSA算法

-MD5 用的是 哈希函数,它的典型应用是对一段信息产生 信息摘要,严格来说,MD5 不是一种 加密算法 而是 摘要算法。无论是多长的输入,MD5 都会输出长度为 128bits 的一个串 (通常用 16 进制 表示为 32 个字符)。

-SHA1 是和 MD5 一样流行的 消息摘要算法,然而 SHA1 比 MD5 的 安全性更强。对于长度小于 2 ^ 64 位的消息,SHA1 会产生一个 160 位的 消息摘要。基于 MD5SHA1 的信息摘要特性以及 不可逆 (一般而言),可以被应用在检查 文件完整性 以及 数字签名 等场景。

-RSA 加密算法是目前最有影响力的 公钥加密算法,并且被普遍认为是目前 最优秀的公钥方案 之一。RSA 是第一个能同时用于 加密 和 数字签名 的算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO 推荐为公钥数据加密标准。

混合加密

anyway,以上都不是今天的重点,可以看出,以上的算法在前后端通信这种场景中都有各种各样的缺点。

混合加密是一种结合了对称加密非对称加密优点的加密方法。它利用非对称加密来安全地交换对称加密的密钥,然后使用该对称密钥来加密实际的消息数据。这种方法结合了非对称加密的安全性和对称加密的效率。

混合加密在多种场景中都有应用,尤其是在需要安全传输大量数据的情况下。例如,SSL/TLS协议就是混合加密的一个典型应用,它在互联网通信中提供了安全的数据传输。在SSL/TLS中,客户端和服务器之间的所有数据传输都是通过混合加密来保护的。此外,电子邮件加密标准如S/MIMEPGP也使用了混合加密来确保邮件内容的机密性和完整性。

很明显,我们可以选择混合加密,作为前后端通信的加密手段。以下是加密交互流程。

image.png

  1. 客户端使用MD5算法计算请求体hash值
  2. 客户端使用自身SM2私钥对请求体进行签名
  3. 客户端生成SM4密钥,使用SM4密钥加密请求体和签名数据
  4. 客户端使用服务端的SM2公钥加密SM4密钥
  5. 将密钥和加密后的请求体发送到服务端
  6. 服务端使用自身SM2私钥对SM4密钥解密
  7. 服务端使用SM4密钥解密请求体
  8. 使用客户端SM2公钥进行验签
  9. 验签通过获得请求体。再使用镜像逻辑对响应体进行加密

可以看到,这一套流程下来,每一次请求和响应SM4钥匙都是新的,不用担心被解密。并且SM2密钥对也可以缓存到本地或者redis,和登录态保持一致的过期时间。

mix-encryption

怎么样?上面的图看起来是不是有点麻烦?没关系,我这里准备了一个现成的npm包,你只需要下载安装即可,支持在node端客户端配对使用,欢迎帮忙试错

  • 基于SM2SM4算法实现
  • 依赖sm-crypto-v2md5
  • 单例模式,一个APP仅有一个实例
  • TS开发,完善的类型支持
pnpm install mix-encryption
    // 客户端 与 服务端操作相同
    import getCryptoInstance from "mix-encryption";
    
    // instance实际是单例的,这里仅作演示 
    
    // 支持传入本地缓存的密钥对 可选参数
    const client = getCryptoInstance({
      cipherMode: 1,
      selfPriKey: "your_private_key",
      selfPubKey: "your_public_key",
      partnerKey: "partner_public_key",
    });

    // client 首次使用 初始化密钥对
    const { publicKey } = client.generateSM2Key();

    // client 将公钥发给服务端配对使用
    sendToServer({ publicKey });

    // server 生成自己的密钥对,并存储客户端公钥
    const server = getCryptoInstance();
    server.acceptPartnerKey(publicKey);

    // server 服务端使用密钥加密返回体
    const { encryptedData, encryptKey } = server.mixCryptoEnCrypto({
      data: "data",
      publicKey: server.publicKey,
    });

    // server 将公钥返回给客户端
    sendToClient({ encryptedData, encryptKey });

    // client 从服务端获取服务端公钥配对,第一次解密不用验签
    const decryptedData = client.mixCryptoDeCrypto(
      encryptedData,
      encryptKey,
      false
    );

    // client 完成配对
    client.acceptPartnerKey(decryptedData.publicKey);
    
   
    // 重置密钥对
    // 将新的密钥通过某次请求发送给后端,通过中间件处理
    // 或者新增接口专门处理
    function send(encryptedData) {
      const res = server.mixCryptoDeCrypto(
        encryptedData.encryptedData,
        encryptedData.encryptKey
      );
      const { publicKey } = server.generateSM2Key();
      server.acceptPartnerKey(res.key);
      return publicKey;
    }

    await client.renewKeyPair(send);