用密文跟后端互通,增强交互安全

388 阅读6分钟

背景

用户与后端交互时,明文传输敏感信息,存在被监听的风险。

方案

用户与后端交互时,使用密文进行传输。密文由前端生成,后端解密。 我这边使用的是gm-crypto,当然也有很多优秀的库。gm-crypto是一个基于Node.js的密码学库,专为与国密算法(中国密码算法标准)兼容而设计。它提供了各种加密、解密、签名和验证功能。

npm install gm-crypto
//或者
yarn add gm-crypto

内部方法介绍

SM2加密与解密

SM2是一种基于椭圆曲线密码学的非对称加密算法。gm-crypto提供了SM2的密钥生成、加密、解密等功能。通过调用相关方法,开发者可以轻松地生成SM2密钥对,并使用公钥进行加密、私钥进行解密。

import { SM2 } from 'gm-crypto';

const { publicKey, privateKey } = SM2.generateKeyPair()// 生成密钥对
const originalData = 'SM2 椭圆曲线公钥密码算法' // 待加密的信息 

const encryptedData = SM2.encrypt(originalData, publicKey, {// 使用公钥加密  
  inputEncoding: 'utf8',
  outputEncoding: 'base64'
})
console.log('encryptedData:', encryptedData)// 输出加密信息

const decryptedData = SM2.decrypt(encryptedData, privateKey, {// 使用私钥解密  
  inputEncoding: 'base64',
  outputEncoding: 'utf8'
})
console.log('decryptedData:', decryptedData)// 输出原始信息

SM3摘要算法

SM3是一种密码杂凑算法,用于生成消息的摘要值。gm-crypto提供了SM3的摘要计算功能,开发者可以通过调用相关方法计算任意消息的SM3摘要值。

import { SM3 } from 'gm-crypto';

console.log(SM3.digest('abc'))// 计算SM3摘要值 
console.log(SM3.digest('YWJj', 'base64'))
console.log(SM3.digest('616263', 'hex', 'base64'))

SM4分组加密算法

SM4是一种分组密码算法,适用于无线局域网等场景。gm-crypto提供了SM4的加密与解密功能,开发者可以使用SM4密钥对数据进行加密和解密操作。

import { SM2, SM4 } from 'gm-crypto';

const key = '0123456789abcdeffedcba9876543210' // 任意32位十六进制数字的字符串
const originalData = 'SM4 国标对称加密'

/**
 * 分组密码模式:
 * - ECB: 电子密码本
 * - CBC: 密码块链接
 */

let encryptedData, decryptedData

// ECB
encryptedData = SM4.encrypt(originalData, key, {
  inputEncoding: 'utf8',
  outputEncoding: 'base64'
})
decryptedData = SM4.decrypt(encryptedData, key, {
  inputEncoding: 'base64',
  outputEncoding: 'utf8'
})
console.log('decryptedData:', decryptedData)

// CBC
const iv = '0123456789abcdeffedcba9876543210' // 初始化向量(32个十六进制数字的任意字符串)
encryptedData = SM4.encrypt(originalData, key, {
  iv,
  mode: SM2.constants.CBC,
  inputEncoding: 'utf8',
  outputEncoding: 'hex'
})
decryptedData = SM4.decrypt(encryptedData, key, {
  iv,
  mode: SM2.constants.CBC,
  inputEncoding: 'hex',
  outputEncoding: 'utf8'
})
console.log('CBC:', decryptedData)

接口参数加密

接口调用,可以使用Axios作为HTTP客户端与后端进行数据交换。为了对接口参数进行加密,你可以创建一个Axios拦截器来在发送请求前加密参数。


import axios from 'axios';
import { SM2 } from 'gm-crypto';
 
// 加密函数
function encryptParams(params) {
  const secretKey = 'your-secret-key'; // 替换为你的密钥
  params.appKey = 'your-app-key';//保证用户的合法性
  params.timestamp = Date.now(); // 添加时间戳,过滤超时请求。
  params.nonce = Math.random().toString(36).substr(2, 10); // 添加随机数,防止重放攻击。确保请求唯一性。

  // 与用户的appKey对应,方便服务端找到对应appSecret。appSecret只参与加密,不参与传输保证用户的安全
  let appSecret = 'your-app-secret'; 
  const encryptedParams = SM2.encrypt(JSON.stringify({...params,appSecret}), secretKey, {
    inputEncoding: 'utf8',
    outputEncoding: 'base64'
  });
  params.sign = encryptedParams;
  return params;
}
 
// 创建Axios实例
const http = axios.create({
  baseURL: 'http://your-api-url/',
  timeout: 10000,
});
 
// 请求拦截器
http.interceptors.request.use(config => {
  // 如果是POST请求且有data,加密data
  if (config.method === 'post' && config.data) {
    config.data = encryptParams(config.data);
  }
  return config;
}, error => {
  return Promise.reject(error);
});
 
export default http;

使用 JSEncrypt 插件

JSEncrypt 是一个用于在客户端进行加密的 JavaScript 库。它基于 RSA 加密算法,可以用于在浏览器中对数据进行加密和解密操作。

安装 JSEncrypt

npm install jsencrypt

//node
npm install node-rsa

枚举公/私钥

RSA在线加密解密 RSA密钥生成器:www.bchrt.com/tools/rsa/

// 设置公钥、私钥(通常从服务器获取,这里仅为示例)
const publicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqdMFWEi2KSfZ/Kx8pUaG1r8KzX4bKnQE6ijqeYbyU8LU8SoXg91EufP39fUFHGzR/zEhLZUyFAxa8YqV0StWoDLnsDShGv/gP1E5XH41uRP8rQE7CRuXOnMNyz+RdMZhCfccj1lA6JpLMqb2KRJH0VDGKcydm2b8wsinkOcyh03VBzmppqCVv7tUsP3fKRXHUmEAEqqMF2bM+UUamKNOqS41wikf1+Bfpwke3mJEx0p9h2+/oFDUR4Sj7DRjJsbVX402WC5Ic6p2dk5ZqC4VYsQAvQ+RJkMz3ATwIpSGegMH2z6VvystTf21XweHWSd3Nrk58jVeVWZSK7XNVnorDQIDAQAB';
const privateKey = 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCp0wVYSLYpJ9n8rHylRobWvwrNfhsqdATqKOp5hvJTwtTxKheD3US58/f19QUcbNH/MSEtlTIUDFrxipXRK1agMuewNKEa/+A/UTlcfjW5E/ytATsJG5c6cw3LP5F0xmEJ9xyPWUDomksypvYpEkfRUMYpzJ2bZvzCyKeQ5zKHTdUHOammoJW/u1Sw/d8pFcdSYQASqowXZsz5RRqYo06pLjXCKR/X4F+nCR7eYkTHSn2Hb7+gUNRHhKPsNGMmxtVfjTZYLkhzqnZ2TlmoLhVixAC9D5EmQzPcBPAilIZ6AwfbPpW/Ky1N/bVfB4dZJ3c2uTnyNV5VZlIrtc1WeisNAgMBAAECggEAQ0hMrM6aBTI5xkbcV2raz0f/UqzsvGQc8lzlkOJrLc5aKXYgvOi0auwCyJqbvMhMZsujNnkOzyyZ92qNssJ13qBsdL5VOeo4gT1/+WozhLErgaukJRaeUtT0mRsFWPujxJv3379f3QgMVPQbQOhyNbbH3BWqN8CU0eNHqDxBI6OIS+84qJfPWCp9sCqhKgyp8GWQIV5QYKqZe6WiFgcNQSPAPNqbS98Iu1sSp88XtBOQuP26+FJLwgA3m+6Xzz1VYIQ8caXJR1nAOWLrKYP3hD7sSh539eFkIVz4wM7KpgJQsq3G4quwcn8OYsknOvDg3Jtz3isJ5vz55N32SuomQQKBgQDUpmtbJiNxgT1rpOAf25TeJ4zse4lOKm38A5EDRPYlPa2SjQxQnSOPzBmXrKg9As0yQZ2b12+/Nj1uR8dqL0KPknp5dPVAqwAROObQHKaXVU1fX0WHWgmnVJErPY1Fx2l38B/4CDM5CDIX5CTLlu46Z9kV5AW3rGXRAVd/0+UHsQKBgQDMcaaJqQk5lIRXEnFcn3d3MRWOwUZGwomFhdtkt4L4dMUyPffEIgRG52jlxFvJ9QZVCbac1pTKDdZkWEyOqgexVtzMHJeuftuPCt+cHayc4YP+k7PzzsttzLk6RfX5eaHn+Rh31JSjl1PuyEvsaf2R+plYcTmZNi4npLmrIuwMHQKBgQDP7CjsZs4hHY1Aw2WhwMI+tc1FLUWD+FiyIYqY98T1hOlCckeoSv2KjLY5Z8jYXvTkPuQsjOnmvNI0ElsZqDwcxcybFZD4OjtGm6yTsG/zaKTOrAj/3zmxMy7+aKDJqcGmVFPt4Za0HR9d3OVda6Zj1Uad8ObmQNsNEZLMmhNHAQKBgEB2NUSCf94f7fpmXkgOH+T9HQb9+eKlxCbNpfeDxVABwYut7klxUFfq7zBDUY3ELFLz1RhusEtlQYwyKzFweyx3bvcMrObgKOpZS1g2Iw70LGJAwCTHt2zPeDffh0c6CsrRx91Asf1C4bwQe5/3a3Rzx5YTXU3pv3PQhJppUHQhAoGBAJ64WD4sy49llEyO8oqD0bjagMOp/Wy6U+ZnDLiW2hd/BCnt4L+poZmTJq8200ClUZOZ5VzmULto6K06ke9LXwc+Qu7C6hCys2MeYOkpgzXZcDTEZyUjjGCHmWWudDOL4XfxoIEDuVJBDXXWO65ciCzL5wS/Wo+re4F0mnQyCHk3';
 
 
export {
  publicKey,
  privateKey
};

封装加密解密方法

import { JSEncrypt } from 'jsencrypt';
import { publicKey, privateKey } from './key.js';
 
// 加密
function encryptText(text) {
  const instance = new JSEncrypt();
  instance.setPublicKey(publicKey);
  return instance.encrypt(text);
}
 
// 解密
function decryptText(text) {
  const instance = new JSEncrypt();
  instance.setPrivateKey(privateKey);
  return instance.decrypt(text);
}
 
export {
  encryptText,
  decryptText,
}

组件中使用

import {encryptText,decryptText} from './rsa/index.ts';
const password = '123456';// 加密的密码
const encPass = encryptText(password) || '';
const decPass = decryptText(encPass);
console.log('加密的密码:', encPass);
console.log('解密后的密码:', decPass);

node解密

const NodeRSA = require('node-rsa');
const fs = require('fs');
 
// const privateKey = fs.readFileSync('./private.pem', 'utf-8');
const publicKey = fs.readFileSync('./public.pem', 'utf-8');
 
const nodersa = new NodeRSA(publicKey);
// 因为jsencrypt 默认使用的是pkcs1 加密方式,所以这里也要使用pkcs1 解密方式,否则会解密失败;
nodersa.setOptions({ encryptionScheme: 'pkcs1' });
 
const decryptText = nodersa.decrypt(pwd, 'utf-8');
console.log('解密后的密码:', decryptText);