前后端RSA加密传输

3,171 阅读2分钟

前端和后端都有RSA加密库,但是通常不能通用,即浏览器的RSA库不能用在Node,Node的RSA库不能用在浏览器。在前后端之间进行RSA加密通信时,需要两端的库的配置相匹配。

本文以前端的 jsencrypt 库和后端的原生 crypto 库举例,讨论下前后端RSA加密通信的方法。

方法

后端生成公钥和密钥

我们首先建立一个服务器:

import Express from 'express'
import Path from 'path'
import BodyParser from 'body-parser'

const cwd = globalThis.process.cwd()
const express = new Express()
const router = new Express.Router()

router.get('/', async (req, res) => {
  res.sendFile(Path.resolve(cwd, './index.html'))
})
router.get('/jsencrypt.js', (req, res) => {
  res.sendFile(Path.resolve(cwd, './lib/jsencrypt.js'))
})

express.use(BodyParser.text())
express.use(router)

express.listen(8000)

服务器提供了index.htmljsencrypt.js文件。

接下来用crypto库来创建公钥和密钥:

import Crypto from 'crypto'

const { publicKey, privateKey } = Crypto.generateKeyPairSync('rsa', {
  modulusLength: 1024,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs1',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase: ''
  }
})

Crypto.generateKeyPairSync文档:nodejs.cn/api/crypto.…

因为 jsencrypt 库的要求,公钥和密钥的配置必须如此。主要是 1024 未的公钥长度(modulusLength)和 spki的编码方式。

然后配置一个获取公钥的路由:

router.get('/key', (req, res) => {
  res.send(publicKey)
})

前端获取公钥并加密明文

在浏览器端,我们先引入 jsencrypt:

<script src="/jsencrypt.js"></script>

接下来获取公钥:

let publicKey = await (await fetch('/key')).text()

使用 jsencrypt 加密:

const value = 'Hack the world'

let jsEncrypt = new JSEncrypt()
jsEncrypt.setPublicKey(publicKey)
let result = jsEncrypt.encrypt(value)

把加密后的文本发送到后端,看后端能否解密:

let res = await (
  await fetch('/encrypt', {
    method: 'POST',
    body: result
  })
).text()

后端解密

后端接收到密文后使用密钥解密:

router.post('/encrypt', (req, res) => {
  let value = Crypto.privateDecrypt(
    {
      key: privateKey,
      passphrase: '',
      padding: Crypto.constants.RSA_PKCS1_PADDING
    },
    Buffer.from(req.body, 'base64')
  )

  res.send(value)
})

注意这里的配置:

  • padding: Crypto.constants.RSA_PKCS1_PADDING
  • Buffer.from(req.body, 'base64')

仍然是为了和 jsencrypt 配合而不能配置为其它值。

如果后端返回了 Hack the world,那么久说明整个加解密的过程成功了。

代码

样例代码:

后端

import Express from 'express'
import Path from 'path'
import BodyParser from 'body-parser'
import Crypto from 'crypto'

const cwd = globalThis.process.cwd()
const express = new Express()
const router = new Express.Router()

const { publicKey, privateKey } = Crypto.generateKeyPairSync('rsa', {
  modulusLength: 1024,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs1',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase: ''
  }
})

console.log('> Private key')
console.log(privateKey)

router.get('/', async (req, res) => {
  res.sendFile(Path.resolve(cwd, './index.html'))
})
router.get('/jsencrypt.js', (req, res) => {
  res.sendFile(Path.resolve(cwd, './lib/jsencrypt.js'))
})
router.get('/key', (req, res) => {
  res.send(publicKey)
})
router.post('/encrypt', (req, res) => {
  let value = Crypto.privateDecrypt(
    {
      key: privateKey,
      passphrase: '',
      padding: Crypto.constants.RSA_PKCS1_PADDING
    },
    Buffer.from(req.body, 'base64')
  )

  res.send(value)
})

express.use(BodyParser.text())
express.use(router)

express.listen(8000)

前端

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <script src="/jsencrypt.js"></script>
    <script type="module">
      const value = 'Hack the world'

      console.log('> Text')
      console.log(value)

      let publicKey = await (await fetch('/key')).text()

      console.log('> Public key')
      console.log(publicKey)

      let jsEncrypt = new JSEncrypt()
      jsEncrypt.setPublicKey(publicKey)
      let result = jsEncrypt.encrypt(value)
      console.log('> Encrypted text')
      console.log(result)

      let res = await (
        await fetch('/encrypt', {
          method: 'POST',
          body: result
        })
      ).text()

      console.log('> Decrypted text')
      console.log(res)
    </script>
  </head>
  <body> </body>
</html>