在开发接口的过程中,难免会遇到隐私数据的传输,如账号密码、支付密码等。以明文的方式传输这些信息,会有泄露给别人的风险,这是就需要加密传输

我们要首先选择加密算法

加密算法分为三种:

  1. 对称加密
    对称加密(也叫私钥加密)指加密和解密使用相同密钥的加密算法。有时又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,同时解密密钥也可以从加密密钥中推算出来。而在大多数的对称算法中,加密密钥和解密密钥是相同的,所以也称这种加密算法为秘密密钥算法或单密钥算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。常见的算法主要有 AESDES
  2. 非对称加密
    非对称加密算法,又称为公开密钥加密算法。它需要两个密钥,一个称为公开密钥 (public key),即公钥;另一个称为私有密钥 (private key),即私钥。 他俩是配对生成的,就像钥匙和锁的关系。因为加密和解密使用的是两个不同的密钥,所以这种算法称为非对称加密算法。其优点是算法强度复杂、安全性高;缺点是加解密速度没有对称加密算法快。常见的算法主要有:RSA、DSA
  3. 摘要加密
    摘要算法是一种能产生特殊输出格式的算法,这种算法的特点是:无论用户输入什么长度的原始数据,经过计算后输出的密文都是固定长度的,这种算法的原理是根据一定的运算规则对原数据进行某种形式的提取,这种提取就是摘要,被摘要的数据内容与原数据有密切联系,只要原数据稍有改变,输出的“摘要”便完全不同,因此,基于这种原理的算法便能对数据完整性提供较为健全的保障。
    但是,由于输出的密文是提取原数据经过处理的定长值,所以它已经不能还原为原数据,即消息摘要算法是不可逆的,理论上无法通过反向运算取得原数据内容,因此它通常只能被用来做数据完整性验证。常见的算法主要有MD5、SHA-1、SHA-256、HMAC

我们这里使用 非对称加密中的 RSA加密

请求参数加密实现的方法

生成加密的RAS公钥与密钥

见我这篇文章 RAS公钥与密钥生成

前端加密实现

引入 Node.js RSA 库

yarn add node-rsa

设置常量

/**
 * 证书 密钥 加密使用
 * @type {string}
 */
export const RAS_PRIVATE =
  `-----BEGIN PRIVATE KEY-----   ...  -----END PRIVATE KEY-----`

/**
 * 证书 公钥 加密使用
 * @type {string}
 */
export const RAS_PUBLIC =
  `-----BEGIN PUBLIC KEY-----  ...  -----END PUBLIC KEY-----`

加密工具类

import NodeRSA from "node-rsa";
import {RAS_PRIVATE, RAS_PUBLIC} from "@/ras-key.js"


const crypto = {}

/**
 * 加密方法
 * @param cipher
 * @returns {*}
 */
crypto.encrypt = function (cipher = {}) {
  const rsa = new NodeRSA(RAS_PUBLIC)
  rsa.setOptions({encryptionScheme: 'pkcs1'})
  let encrypt = rsa.encrypt(JSON.stringify(cipher), 'base64');
  return {
    encrypt
  }
}


/**
 * 解密方法 
 * @returns {string}
 * @param decryptData
 */
crypto.decryption = function (decryptData  = '') {
  let rsa = new NodeRSA(RAS_PRIVATE)
  rsa.setOptions({ encryptionScheme: 'pkcs1' });
  let data = rsa.decrypt(decryptData, 'utf8');

  return JSON.parse(data);
}

export default crypto

接口调用

SYS_USER_LOGIN(data = {}) {
    // 接口请求
    return request({
      url: '/auth/login',
      method: 'post',
      data: util.crypto.encrypt(data)
    })
  }

这样请求参数就加密了

请求参数加密与返回结果加密_加密算法

后端解密实现

我这里使用Hutool工具类库

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-crypto</artifactId>
    <version>5.7.12</version>
</dependency>

解密实现

// RasConfig.RAS_PRIVATE 为密钥 
// JAVA里的密钥不能带类似 -----BEGIN PRIVATE KEY-----  的头和尾
// encrypt 为密文
RSA rsa = new RSA(RasConfig.RAS_PRIVATE, null);
byte[] decrypt = rsa.decrypt(encrypt, KeyType.PrivateKey);
String decryptSt = StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);

返回结果加密实现

生成加密的RAS公钥与密钥(同上)

后端加密实现

加密的实现

public EncryptParam(Object data) {
    String param = JSONUtil.getBeanToJson(data);
    RSA rsa = new RSA(null,RasConfig.RAS_PUBLIC);
    byte[] encryptByte = rsa.encrypt(param, KeyType.PublicKey);
    this.encrypt =  Base64.encodeBase64String(encryptByte);
}

前台解密的实现

import NodeRSA from "node-rsa";
import {RAS_PRIVATE, RAS_PUBLIC} from "@/ras-key.js"


const crypto = {}

/**
 * 加密方法
 * @param cipher
 * @returns {*}
 */
crypto.encrypt = function (cipher = {}) {
  const rsa = new NodeRSA(RAS_PUBLIC)
  rsa.setOptions({encryptionScheme: 'pkcs1'})
  let encrypt = rsa.encrypt(JSON.stringify(cipher), 'base64');
  return {
    encrypt
  }
}


/**
 * 解密方法 
 * @returns {string}
 * @param decryptData
 */
crypto.decryption = function (decryptData  = '') {
  let rsa = new NodeRSA(RAS_PRIVATE)
  rsa.setOptions({ encryptionScheme: 'pkcs1' });
  let data = rsa.decrypt(decryptData, 'utf8');

  return JSON.parse(data);
}

export default crypto

这样结果就加密了

请求参数加密与返回结果加密_加密算法_02


下面的代码记录我自己的实现

我的思路是所有的加密参数都使用加密的实体类接受,并解析

接口返回的时候有设置一个加密参数,前端请求拦截的时候解密

package org.example.framework.config;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * 读取RAS 公钥密钥
 * Created by myy on 2021/9/16.
 */
@Configuration
@Slf4j
public class RasConfig {

    /**
     * 密钥钥
     */
    public static String RAS_PRIVATE;
    /**
     * 公钥
     */
    public static String RAS_PUBLIC;

    @Bean
    public void getPublicKey() {
        log.info("开始 读取RAS加密公钥");
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ras/rsa_public_key.pem");
        try {
            assert inputStream != null;
            RAS_PUBLIC = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            if (ObjectUtils.isEmpty(RAS_PUBLIC)) {
                log.info("读取RAS加密密钥 未空");
            } else {
                log.info("读取RAS加密密钥 成功");
            }
        } catch (IOException e) {
            log.error("读取RAS加密公钥失败");
            e.printStackTrace();
        } finally {
            try {
                assert inputStream != null;
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Bean
    public void getPrivateKey() {
        log.info("开始 读取RAS加密密钥");
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("ras/rsa_private_key.pem");
        try {
            assert inputStream != null;
            RAS_PRIVATE = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            if (ObjectUtils.isEmpty(RAS_PRIVATE)) {
                log.info("读取RAS加密密钥 为空");
            } else {
                log.info("读取RAS加密密钥 成功");
            }
        } catch (IOException e) {
            log.error("读取RAS加密密钥失败");
            e.printStackTrace();
        } finally {
            try {
                assert inputStream != null;
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

解密的实体

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.example.common.util.JSONUtil;
import org.example.framework.config.RasConfig;
import org.springframework.util.ObjectUtils;
import org.apache.commons.codec.binary.Base64;


/**
 * 加密参数
 * Created by myy on 2021/9/14.
 */
@Getter
@Setter
@Slf4j
public class EncryptParam {

    /**
     * 加密参数
     */
    private String encrypt;


    public EncryptParam() {
    }


    /**
     * 加密参数
     * @param data
     */
    public EncryptParam(Object data) {
        String param = JSONUtil.getBeanToJson(data);
        RSA rsa = new RSA(null,RasConfig.RAS_PUBLIC);
        byte[] encryptByte = rsa.encrypt(param, KeyType.PublicKey);
        this.encrypt =  Base64.encodeBase64String(encryptByte);
    }

    /**
     * 得到解密的内容
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T getParam(Class<T> clazz) {
        try {
            log.info("密文: {}", encrypt);
            // 解密
            RSA rsa = new RSA(RasConfig.RAS_PRIVATE, null);
            byte[] decrypt = rsa.decrypt(encrypt, KeyType.PrivateKey);
            String decryptSt = StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
            if (ObjectUtils.isEmpty(decryptSt)) {
                throw new RuntimeException("解密后字符串为空");
            }
            log.info("解密后内容: {}", decryptSt);

            // 将字符串转换成实体
            T t = JSONUtil.getJsonToBean(decryptSt, clazz);
            if (ObjectUtils.isEmpty(t)) {
                throw new RuntimeException("JSON数据转换成对象失败");
            }
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("解密失败");
            throw new RuntimeException("解密失败");
        }
    }

}

接口参数解密

@RequestMapping("/login")
public ResponseData login(@RequestBody EncryptParam param) {
    LoginDTO loginDTO = param.getParam(LoginDTO.class);
    return new SuccessResponseData(authService.login(loginDTO));
}

修改 Response

package org.example.framework.web.response;

import org.example.framework.web.controller.EncryptParam;

/**
 * Created by myy on 2021/9/6.
 */
public class SuccessResponseData extends ResponseData {

    public SuccessResponseData() {
        super(Boolean.TRUE, "200", "请求成功", (Object) null, traceID());
    }

    public SuccessResponseData(Object object) {
        super(Boolean.TRUE, "200", "请求成功", object, traceID());
    }

    public SuccessResponseData(String code, String message, Object object) {
        super(Boolean.TRUE, code, message, object, traceID());
    }

    /**
     * 参数加密
     */
    public SuccessResponseData encrypt() {
        setEncrypt(true);
        setData(new EncryptParam(getData()));
        return this;
    }


}

接口加密

@RequestMapping("/treeMenu")
public ResponseData treeMenu() {
    return new SuccessResponseData(menuService.treeMenu()).encrypt();
}

前端 工具类

import NodeRSA from "node-rsa";
import {RAS_PRIVATE, RAS_PUBLIC} from "@/ras-key.js"


const crypto = {}

/**
 * 加密方法
 * @param cipher
 * @returns {*}
 */
crypto.encrypt = function (cipher = {}) {
  const rsa = new NodeRSA(RAS_PUBLIC)
  rsa.setOptions({encryptionScheme: 'pkcs1'})
  let encrypt = rsa.encrypt(JSON.stringify(cipher), 'base64');
  return {
    encrypt
  }
}


/**
 * 解密方法
 * @returns {string}
 * @param decryptData
 */
crypto.decryption = function (decryptData  = '') {
  let rsa = new NodeRSA(RAS_PRIVATE)
  rsa.setOptions({ encryptionScheme: 'pkcs1' });
  let data = rsa.decrypt(decryptData, 'utf8');

  return JSON.parse(data);
}

export default crypto

接口加密

SYS_USER_LOGIN(data = {}) {
  // 接口请求
  return request({
    url: '/auth/login',
    method: 'post',
    data: util.crypto.encrypt(data)
  })
}

响应拦截解密

// 响应拦截
service.interceptors.response.use(
  response => {
    // dataAxios 是 axios 返回数据中的 data
    const dataAxios = response.data
    const {success, data, encrypt} = dataAxios
    // 根据 success 进行判断
    if (success) {
      // 判断参数是否加密
      if (encrypt){
        let decryption = util.crypto.decryption(data.encrypt)
        console.log("解密 {}",decryption)
        return decryption
      }else {
        return data
      }
    } else {
      errorCreate(`${dataAxios.message}`)
    }
  },
  error => {
   ...
  }
)