对app请求的参数和响应进行rsa加密和解密

功能实现流程图:

在这里插入图片描述

参考:
链接: https://www.cnblogs.com/throwable/p/9471938.html.
链接: https://www.cnblogs.com/whcghost/p/5657594.html.
链接: https://blog.csdn.net/charry0110/article/details/109495035.
链接: https://www.cnblogs.com/elaron/archive/2013/04/09/3010375.html.
链接: https://blog.csdn.net/qq_35164962/article/details/102704880.

功能说明:

主要是为了实现对app的请求的参数和响应进行加密,本次只做了json格式参数的post请求的加解密。主要考虑到get参数加密后太长会超过url长度限制。上传下载的请求也没有加解密,主要担心加解密时间太长。
除了对参数和响应内容进行加密外,还进行了时间戳的过期检查操作和签名验证的操作,来防止伪造请求访问。但是只对时间戳拼接一个头的字符串进行签名,并没有使用参数内容进行签名,想想只要破解不了我的加密方式,我用时间戳来签名应该就够了。

主要实现代码

json请求接收器

package com.wx.security;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.wx.exception.BadRequestException;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonInputMessage;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Date;

/**
 *
 */
//@ControllerAdvice(basePackages = "com.wx.*.controller")
@ControllerAdvice
public class CustomRequestBodyAdvice extends RequestBodyAdviceAdapter {

    private final ObjectMapper objectMapper;

    public CustomRequestBodyAdvice(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return RsaProperties.enSwitch;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        String content = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("UTF-8"));
        EncryptModel in = objectMapper.readValue(content, EncryptModel.class);
        //先校验一下时间戳是否过期,如果时间戳和后台当前时间相差超过5分钟,则请求判定为失效
        if (Math.abs(in.getTimestamp() - new Date().getTime()) > RsaProperties.deadlineMins * 1000 * 60) {
            throw new BadRequestException("请求已过期!");
        }

        String inRawSign = in.getTimestamp() + "";
        String inSign = null;
        try {
            inSign = ShaUtils.SINGLETON.sha(inRawSign);
        } catch (Exception e) {
            throw new IllegalArgumentException("验证参数签名失败!");
        }
        if (!inSign.equals(in.getSign())) {
            throw new IllegalArgumentException("验证参数签名失败!");
        }
        String decryptedData = null;
        try {
            decryptedData = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, in.getData());
        } catch (Exception e) {
            throw new IllegalArgumentException("参数解密失败:" + in.getData(), e);
        }
        ByteArrayInputStream inputStream = new ByteArrayInputStream(URLDecoder.decode(decryptedData, "UTF-8").getBytes(Charset.forName("UTF-8")));
        return new MappingJacksonInputMessage(inputStream, inputMessage.getHeaders());
    }
}

响应解析器

package com.wx.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;

import java.net.URLEncoder;

/**
 *
 */
@ControllerAdvice
public class CustomResponseBodyAdvice extends JsonViewResponseBodyAdvice {

    private final ObjectMapper objectMapper;

    public CustomResponseBodyAdvice(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return RsaProperties.enSwitch;
    }

    @Override
    protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
                                           MethodParameter returnType, ServerHttpRequest request,
                                           ServerHttpResponse response) {
        if (request.getMethod() == HttpMethod.POST) {
            EncryptModel out = new EncryptModel();
            out.setTimestamp(System.currentTimeMillis());
            String decryptedData = null;
            try {
                final String jsonStr = objectMapper.writeValueAsString(bodyContainer.getValue());
                decryptedData = RsaUtils.encryptByPublicKey(RsaProperties.publicKey, URLEncoder.encode(jsonStr, "UTF-8"));
                out.setData(decryptedData);
            } catch (Exception e) {
                throw new IllegalArgumentException("返回对象加密失败:" + bodyContainer.toString(), e);
            }
            String rawSign = out.getTimestamp() + "";
            try {
                out.setSign(ShaUtils.SINGLETON.sha(rawSign));
            } catch (Exception e) {
                throw new IllegalArgumentException("返回对象签名失败:" + rawSign);
            }
            bodyContainer.setValue(out);
        }
    }
}

加密后接收对象

package com.wx.security;

/**
 * @Author: 遛猫达人
 * @Description: TODO
 * @DateTime: 2021/8/9 11:44
 **/
public class EncryptModel {
    private String data;
    private Long timestamp;
    private String sign;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }
}

签名工具:

package com.wx.security;

import org.apache.commons.codec.binary.Hex;

import java.security.MessageDigest;

public enum ShaUtils {

    /**
     * SINGLETON
     */
    SINGLETON;

    private static final String SECRET = "xxx";
    private static final String CHARSET = "UTF-8";

    /**
     * @param raw
     * @return
     * @throws Exception
     */
    public String sha(String raw) throws Exception {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update((SECRET + raw).getBytes(CHARSET));
        return Hex.encodeHexString(messageDigest.digest());
    }

}

前端加密

  post(api, data, options) {
    var newData = {};
    if(store.state.enSwitch){
      let jsonData = JSON.stringify(data);
      // console.log(jsonData)
      newData.timestamp = new Date().getTime();
      let shaContent = sha_prefix + newData.timestamp;
      
      newData.sign =sha256(shaContent);
      newData.data = encrypt(encodeURIComponent(jsonData));
    }else{
      newData = data
    }
  
    return this.getRequest(api, newData, options, 'POST') 
  },

前端解密

          //解密post请求响应
          if(store.state.enSwitch && method === 'POST'){
            let sign =sha256(sha_prefix + res.data.timestamp);
            if(sign !== res.data.sign){
              reject(res)
              uni.showToast({
                icon: 'none',
                title: "签名校验不通过"
              })
              return false
            }
            let resData = decrypt(res.data.data);
            // console.log(api+":"+decodeURIComponent(resData))
            res.data = JSON.parse(decodeURIComponent(resData))
          }

注意:使用前端加解密工具前先要执行以下命令安装相关依赖包:
npm install jsencrypt
npm install js-sha256
npm i encryptlong -S
前端加解密工具:

import JSEncrypt from 'encryptlong'

const publicKey = 'xxx'

const privateKey = 'yyyy'

// 加密
export function encrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // 设置公钥,注意此处不要再设置私钥
  return encryptor.encryptLong(txt) // 对需要加密的数据进行加密
}

// 解密
export function decrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPrivateKey(privateKey)// 设置私钥,注意此处不要再设置公钥
  return encryptor.decryptLong(txt)
}

后端加解密工具:

package com.wx.security;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author
 * @description Rsa 工具类,公钥私钥生成,加解密
 * @date
 **/
public class RsaUtils {

    private static final String SRC = "1123456";
    public static final String RSA = "RSA";
    /** */
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /** */
    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;
    public static final String CHARSET_NAME = "UTF-8";

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        System.out.println("\n");
        RsaKeyPair keyPair = generateKeyPair();
        System.out.println("公钥:" + keyPair.getPublicKey());
        System.out.println("私钥:" + keyPair.getPrivateKey());
        System.out.println("\n");
        test1(keyPair);
        System.out.println("\n");
    }

    /**
     * 公钥加密私钥解密
     */
    private static void test1(RsaKeyPair keyPair) throws Exception {
        System.out.println("***************** 公钥加密私钥解密开始 *****************");
        String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
        String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
        System.out.println("加密前:" + RsaUtils.SRC);
        System.out.println("加密后:" + text1);
        System.out.println("解密后:" + text2);
        if (RsaUtils.SRC.equals(text2)) {
            System.out.println("解密字符串和原始字符串一致,解密成功");
        } else {
            System.out.println("解密字符串和原始字符串不一致,解密失败");
        }
        System.out.println("***************** 公钥加密私钥解密结束 *****************");
    }

    /**
     * 私钥解密
     *
     * @param privateKeyText 私钥
     * @param text           待解密的文本
     * @return /
     * @throws Exception /
     */
    public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(privateKeyText);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        byte[] encryptedData = Base64.decodeBase64(text);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return new String(decryptedData, CHARSET_NAME);
    }


    /**
     * <p>
     * 公钥加密
     * </p>
     *
     * @param str       源数据
     * @param publicKey 公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static String encryptByPublicKey(String publicKey, String str)
            throws Exception {
        byte[] data = str.getBytes(CHARSET_NAME);
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        // 对数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return Base64.encodeBase64String(encryptedData);
    }

    /**
     * 构建RSA密钥对
     *
     * @return /
     * @throws NoSuchAlgorithmException /
     */
    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
        return new RsaKeyPair(publicKeyString, privateKeyString);
    }


    /**
     * RSA密钥对对象
     */
    public static class RsaKeyPair {

        private final String publicKey;
        private final String privateKey;

        public RsaKeyPair(String publicKey, String privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }

        public String getPublicKey() {
            return publicKey;
        }

        public String getPrivateKey() {
            return privateKey;
        }

    }
}

碰到的问题:

  • rsa加密字符串的长度最大为117个字节,所以前后端都必须使用分段加密和分段解密
  • 如果加密内容有中文,一定要先用js的encodeURIComponent方法或者java的URLEncoder.encode方法编码后再加密,然后用js的decodeURIComponent方法或者java的URLDecoder.decode方法解密,否则直接对中文加解密时偶尔会失败。
  • 公钥和私钥对的生成一定要小心,我刚开始使用命令在linux上生成的公钥和私钥,但是前端能加密,后端不能解密,后来发现生成的密钥对类型有问题,后端代码无法用它来解密,然后改成了用后端代码来生成密钥对。
  • 前端加解密方法中官方网站推荐把公钥和私钥都设置到JSEncrypt对象中,再进行加解密,说这样可以提高加解密的概率。但是我这样设置了之后java后台代码无法解密,然后改成了前端加密就只设置公钥,前端解密就只设置私钥就正常了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值