【利用hutool简单演示前后端加解密流程】

一、流程:

① 前端使用AES加密数据,并将AES KEY做RSA加密,最后用SM4加密数据结构并发送到后端

② 后端使用约定好的SM4加密的种子,解密出请求数据结构;使用RSA私钥解密出AES KEY,并用AES KEY + 约定好的盐值获取方式,解密出数据体,最后保存AES KEY到ThreadLocal中

③ 后端准备响应数据,从ThreadLocal中获取AES KEY,利用前后端约定好的盐值获取方式,加密响应数据

④ 前端获取到响应数据,利用之前生成的AES KEY和盐,解密出响应数据

二、代码:

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SM4;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.RandomStringUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 演示前后端加解密过程
 */
public class TestEncryptionAndDecryption {

    private static final String RSA_PUBLIC_KEY;

    private static final String RSA_PRIVATE_KEY;

    private static final DateTimeFormatter YYYYMMDDHHMMSS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private static final DateTimeFormatter YYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd");

    static {
        RSA rsa = new RSA();
        RSA_PUBLIC_KEY = rsa.getPublicKeyBase64();
        RSA_PRIVATE_KEY = rsa.getPrivateKeyBase64();
    }

    public static void main(String[] args) {
        // 前端将数据发送到服务器 ==============================>
        // 模拟前端加密
        String encryptedRequestStr = testFrontEncryption(REQUEST_DATA);
        System.out.println("前端加密后的请求内容:\n" + encryptedRequestStr);
        System.out.println();

        // 模拟后端解密
        Map<String, String> result = testBackendDecryption(encryptedRequestStr);
        System.out.println("后端解密后的请求内容:\n" + result.get("decryptedRequestStr"));
        System.out.println();


        // 服务器返回加密数据 ==============================>
        // 模拟后端加密响应数据
        String encrptedResponseStr = testBackendEncryption(RESPONSE_DATA, result.get("aesKeyStr"));
        System.out.println("后端加密后的响应内容:\n" + encrptedResponseStr);
        System.out.println();

        // 模拟前端解密响应数据
        String decrptedResponseStr = testFrontDecrption(encrptedResponseStr, result.get("aesKeyStr"));
        System.out.println("前端解密后的响应内容:\n" + decrptedResponseStr);
    }

    // 模拟前端解密AES加密后的数据
    // 流程
    // 1、前端发出请求的时候,生成了AES KEY 和 IV
    // 2、后端又是使用前端生成的AES KEY + IV 加密的响应数据
    // 3、因此,前端拿到响应数据后,可以直接做解密获得响应数据
    static String testFrontDecrption(String cipherStr, String aesKeyStr) {
        // 根据 aesKeyStr 生成偏移量iv
        byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKeyStr, YYYYMMDD.format(LocalDate.now()))));
        // aesKeyStr是base64编码的,下面解码为字节数组
        byte[] aesKey = Base64.decode(aesKeyStr.getBytes(CharsetUtil.CHARSET_UTF_8));
        // 响应数据做AES解密
        AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKey, iv);
        // 获取AES解码后的响应数据
        return aes.decryptStr(cipherStr);
    }

    // 后端加密响应数据
    // 请求和响应是一套完整的流程,在请求中,通过RSA解密得到的AES KEY,可以通过某种方式传递到响应返回的时候使用,给响应数据做加密
    static String testBackendEncryption(String plainStr, String aesKeyStr) {
        // 根据 aesKeyStr 生成偏移量iv
        byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKeyStr, YYYYMMDD.format(LocalDate.now()))));
        // aesKeyStr是base64编码的,下面解码为字节数组
        byte[] aesKey = Base64.decode(aesKeyStr.getBytes(CharsetUtil.CHARSET_UTF_8));
        // 响应数据做AES加密
        AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKey, iv);
        // 返回Base64编码的响应数据
        return aes.encryptBase64(plainStr);
    }

    // 前端加密数据请求服务器
    // 流程:
    // 1、生成一个随机的AES KEY
    // 2、获取AES KEY的盐(前后端获取盐的方式应该一致,这里是利用了当前日期的字符串)
    // 3、请求数据使用 AES KEY 和 盐 做AES对称加密
    // 4、为了可以解密响应的加密数据,前端将AES KEY使用RSA非对称加密后传给后端,这样后端可以使用这个AES KEY 和约定的盐,对响应数据加密,前端可以用相同KEY和IV解密响应数据
    // 5、为了隐藏请求数据的结构,前端使用SM4国密算法,利用当前日期做种子,加密请求数据结构并向后端发送请求
    static String testFrontEncryption(String plainStr) {
        // 使用 UUID + 当前时间字符串 + 32位随机字符串,生成一个 AES 密钥
        String dateTimeStr = YYYYMMDDHHMMSS.format(LocalDateTime.now());
        String randomStr = RandomStringUtils.random(32, true, true);
        byte[] aesKey = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}-{}-{}", UUID.randomUUID().toString(), dateTimeStr, randomStr)));

        // 使用当前日期的字符串,生成 AES密码 的偏移量(盐) iv
        String base64AesKey = Base64.encode(aesKey);
        String dateStr = YYYYMMDD.format(LocalDate.now());
        byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", base64AesKey, dateStr)));

        // 对内容做AES加密
        AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKey, iv);
        String data = aes.encryptHex(plainStr);

        // 使用前后端约定好的公钥,对AES密钥做RSA加密
        RSA rsa = new RSA(RSA_PRIVATE_KEY, RSA_PUBLIC_KEY);
        String key = rsa.encryptHex(base64AesKey, KeyType.PublicKey);

        // 组装请求数据
        Map<String, String> reqDataObj = new HashMap<>();
        reqDataObj.put("key", key);
        reqDataObj.put("data", data);

        // 对请求数据的结构做简单的SM4加密
        String sm4Key = SecureUtil.md5(dateStr);
        SM4 sm4 = new SM4(Mode.ECB, Padding.PKCS5Padding, HexUtil.decodeHex(sm4Key));

        return sm4.encryptHex(JSON.toJSONString(reqDataObj));
    }

    // 后端解密前端的请求数据
    // 流程
    // 1、其实就是前端加密过程的逆向
    // 2、首先做SM4解密,由于前后端约定使用当前日期做种子加解密请求数据结构,因此,首先做SM4解密,得到请求数据结构体
    // 3、前端对AES KEY做了RSA加密,公钥是前后端约定好的,因此,后端使用私钥直接做RSA解密,得到AES KEY
    // 4、用前后端约定好的方式,通过当前日期,获得AES KEY的IV
    // 5、使用AES KEY 和 IV 解密出请求数据
    // 6、后端保存AES KEY,以在返回的时候加密响应数据
    static Map<String, String> testBackendDecryption(String cipherStr) {
        // 将SM4加密过的数据,用同样的KEY做解密,这个KEY就是当前日期的字符串做MD5加密后的值
        String dateStr = YYYYMMDD.format(LocalDate.now());
        String sm4Key = SecureUtil.md5(dateStr);
        SM4 sm4 = new SM4(Mode.ECB, Padding.PKCS5Padding, HexUtil.decodeHex(sm4Key));
        // 得到SM4解密后的数据,即reqDataObj的JSON串
        String decryptStr = sm4.decryptStr(cipherStr);

        // 由于前后端约定,数据传输使用JSON,因此,将做了SM4解密后的数据转换成JSONObject
        JSONObject reqDataObj = JSON.parseObject(decryptStr);
        // 得到RSA加密后的KEY
        String key = reqDataObj.getString("key");
        // 得到AES加密后的VALUE
        String data = reqDataObj.getString("data");

        // 使用私钥解密出RSA加密后的AES KEY
        RSA rsa = new RSA(RSA_PRIVATE_KEY, RSA_PUBLIC_KEY);
        // 得到可以解密AES加密后的VALUE的KEY
        String aesKey = rsa.decryptStr(key, KeyType.PrivateKey, CharsetUtil.CHARSET_UTF_8);

        // 得到偏移量(盐值)
        byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKey, dateStr)));
        // 得到Base64解码后的KEY
        byte[] aesKeyByte = Base64.decode(aesKey);

        // 使用盐值 和 KEY,构造AES对象
        AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKeyByte, iv);
        // 得到AES解密后的值
        String decryptedRequestStr = aes.decryptStr(data);

        Map<String, String> result = new HashMap<>();
        result.put("aesKeyStr", aesKey);
        result.put("decryptedRequestStr", decryptedRequestStr);
        return result;
    }

    // 请求数据
    private final static String REQUEST_DATA = "{\n" +
            "    \"key_1\" : \"value_1\",\n" +
            "    \"key_2\" : \"value_2\",\n" +
            "    \"key_3\" : \"value_3\",\n" +
            "    \"key_4\" : \"value_4\",\n" +
            "    \"key_5\" : \"value_5\",\n" +
            "    \"key_6\" : \"value_6\",\n" +
            "    \"key_7\" : \"value_7\"\n" +
            "}";

    // 响应数据
    private final static String RESPONSE_DATA = "{\n" +
            "    \"key_1\": \"value_1\",\n" +
            "    \"key_2\": \"value_2\",\n" +
            "    \"key_3\": \"value_3\",\n" +
            "    \"key_4\": {\n" +
            "        \"key_4_1\": \"value_4_1\",\n" +
            "        \"key_4_2\": \"value_4_2\",\n" +
            "        \"key_4_3\": \"value_4_3\",\n" +
            "        \"key_4_4\": \"value_4_4\"\n" +
            "    }\n" +
            "}";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值