Skip to content
On this page

前后端加密处理

加密知识基础

对称加密 (加密与解密密钥相同)

  • 常见的 对称加密 算法主要有 DES3DESAES 等,常用于内网环境

非对称加密 (密钥与解密密钥不同,其中公钥和私钥都可以用来加解密)

  • 常见的 非对称加密 算法主要有 RSADSA 等,非常损耗性能, 常用于加密密码等短的字符串

散列算法(不需要密钥)

  • 常见的 散列算法 算法主要有 SHA-1MD5 等,常用于给任意数据签名, 例如大文件

参考juejin

前后端非对称加密的简单实现

node-rsa + jsencrypt实现

yarn add node-rsa node-rsa

服务端生成公钥私钥对

js
const NodeRSA = require('node-rsa')

// b: 生成的密钥长度大小,长度越大可加密的数据(string)就越多. 一般不要超过1024, 对计算机cpu性能会有很大损耗
let key = new NodeRSA({ b: 512 })
key.setOptions({ encryptionScheme: 'pkcs1' }) // 需设置pkcs1加密, 配合前端依赖jsencrypt
let privateKey = key.exportKey('pkcs1-private-pem')
let publicKey = key.exportKey('pkcs8-public-pem')
console.log(privateKey, publicKey) // 得到公钥和私钥保存
const NodeRSA = require('node-rsa')

// b: 生成的密钥长度大小,长度越大可加密的数据(string)就越多. 一般不要超过1024, 对计算机cpu性能会有很大损耗
let key = new NodeRSA({ b: 512 })
key.setOptions({ encryptionScheme: 'pkcs1' }) // 需设置pkcs1加密, 配合前端依赖jsencrypt
let privateKey = key.exportKey('pkcs1-private-pem')
let publicKey = key.exportKey('pkcs8-public-pem')
console.log(privateKey, publicKey) // 得到公钥和私钥保存

前端使用公钥加密

yarn add jsencrypt node-rsa

  • 服务端下发公钥publicKey给前端
js
import JSEncrypt from 'jsencrypt'
// ...
// "publicKey": "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhv......AAQ==\n-----END PUBLIC KEY-----",
const RSAPubEncrypt = new JSEncrypt() // 生成实例
RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!)
const ciphertext = RSAPubEncrypt.encrypt('some msg...') // 加密[有长度限制, 且rsa加密过长字符时会有很大的性能问题(占用cpu)]
console.log('加密后:', ciphertext)
import JSEncrypt from 'jsencrypt'
// ...
// "publicKey": "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhv......AAQ==\n-----END PUBLIC KEY-----",
const RSAPubEncrypt = new JSEncrypt() // 生成实例
RSAPubEncrypt.setPublicKey(publicKey) // 配置公钥(不是将公钥实例化时传入!!!)
const ciphertext = RSAPubEncrypt.encrypt('some msg...') // 加密[有长度限制, 且rsa加密过长字符时会有很大的性能问题(占用cpu)]
console.log('加密后:', ciphertext)

服务端使用私钥解密

js
const NodeRSA = require('node-rsa')
// ...
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwI......-----END RSA PRIVATE KEY-----"
const rsakey = new NodeRSA(privateKey)
rsakey.setOptions({ encryptionScheme: 'pkcs1' }) // 前端使用jsencrypt库必须设置此pkcs1加密(生成密钥时也许对应)
const password = rsakey.decrypt('some msg', 'utf8')

console.log('解密后:', password)
const NodeRSA = require('node-rsa')
// ...
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwI......-----END RSA PRIVATE KEY-----"
const rsakey = new NodeRSA(privateKey)
rsakey.setOptions({ encryptionScheme: 'pkcs1' }) // 前端使用jsencrypt库必须设置此pkcs1加密(生成密钥时也许对应)
const password = rsakey.decrypt('some msg', 'utf8')

console.log('解密后:', password)

node-forge实现

yarn add node-forge

服务端生成公钥私钥对

js
const forge = require('node-forge')

const { rsa, publicKeyToRSAPublicKeyPem, privateKeyToPem } = forge.pki
// 异步操作
rsa.generateKeyPair({ bits: 2048, workers: 2 }, (err, keypair) => {
  if (err) return console.log('生成公私钥失败:', err)
  let privateKey = privateKeyToPem(keypair.privateKey, 72)
  let publicKey = publicKeyToRSAPublicKeyPem(keypair.publicKey, 72)
  console.log(privateKey, publicKey) // 得到公钥和私钥保存
})
const forge = require('node-forge')

const { rsa, publicKeyToRSAPublicKeyPem, privateKeyToPem } = forge.pki
// 异步操作
rsa.generateKeyPair({ bits: 2048, workers: 2 }, (err, keypair) => {
  if (err) return console.log('生成公私钥失败:', err)
  let privateKey = privateKeyToPem(keypair.privateKey, 72)
  let publicKey = publicKeyToRSAPublicKeyPem(keypair.publicKey, 72)
  console.log(privateKey, publicKey) // 得到公钥和私钥保存
})

前端使用公钥加密

同样是使用node-forge

  • 服务端下发公钥publicKey给前端
js
import JSEncrypt from 'jsencrypt'
// ...
// "publicKey": "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhv......AAQ==\n-----END PUBLIC KEY-----",
const RSAPubEncrypt = forge.pki.publicKeyFromPem(publicKey)
// 加密[有长度限制, 且rsa加密过长字符时会有很大的性能问题(占用cpu)]
const ciphertext = RSAPubEncrypt.encrypt('some msg...', 'RSA-OAEP') // RSA-OAEP协议与服务端解密时需对应
console.log('加密后:', ciphertext)
import JSEncrypt from 'jsencrypt'
// ...
// "publicKey": "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhv......AAQ==\n-----END PUBLIC KEY-----",
const RSAPubEncrypt = forge.pki.publicKeyFromPem(publicKey)
// 加密[有长度限制, 且rsa加密过长字符时会有很大的性能问题(占用cpu)]
const ciphertext = RSAPubEncrypt.encrypt('some msg...', 'RSA-OAEP') // RSA-OAEP协议与服务端解密时需对应
console.log('加密后:', ciphertext)

服务端使用私钥解密

js
const forge = require('node-forge')

// ...
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwI......-----END RSA PRIVATE KEY-----"
const rsakey = forge.pki.privateKeyFromPem(privateKey)
const password = rsakey.decrypt('some msg...', 'RSA-OAEP')
console.log('解密后:', password)
const forge = require('node-forge')

// ...
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwI......-----END RSA PRIVATE KEY-----"
const rsakey = forge.pki.privateKeyFromPem(privateKey)
const password = rsakey.decrypt('some msg...', 'RSA-OAEP')
console.log('解密后:', password)

注意

上面Demo中的私钥privateKey和公钥publicKey均为字符串String

RSA加密过长数据的解决方案

  • RSA加解密时对计算机cpu损耗较高. 因此一般只用于密码等较小的数据量.
  • RSA加密的原始信息必须小于Key的长度. RSA可加密数据长度与Key的长度成正比。本机AMD3600 CPU生成2048b的密钥对时,耗时已经超过2s, 加解密也相当费时.
  • 当用512b字节生成的密钥对大小超过54字节(54字母)的字符串加密时抛错:Message too long for RSA

方案

参考https加解密方案,先使用对称加密(例如AES)对随机生成的secret加密,得到的密文再使用对称加密

实现步骤

  • 前端生成随机AES对称加密的字符串密钥
  • 使用这个字符串密钥做AES加密原始数据(不需考虑数据大小问题)
  • 再使用RSA加密字符串密钥(避免了RSA加密原始数据过大的瓶颈问题)
  • 在传输过程中有两组数据:一组是使用了AES加密的原始数据,一组是使用RSA非对称加密的字符串密钥
  • 后端使用RSA密钥解密得到原始字符串密钥,再使用AES解密得到原始数据