zoukankan      html  css  js  c++  java
  • AES与RSA混合加密完整实例

    前后端API交互数据加密——AES与RSA混合加密完整实例

     

      前言

       前段时间看到一篇文章讲如何保证API调用时数据的安全性(传送门:https://blog.csdn.net/ityouknow/article/details/80603617),文中讲到利用RSA来加密传输AES的秘钥,用AES来加密数据,并提供如下思路:

      说人话就是前、后端各自生成自己的RSA秘钥对(公钥、私钥),然后交换公钥(后端给前端的是正常的明文公钥,前端给后端的是用后端公钥加密后的密文公钥;PS:其实我觉得直接交换两个明文公钥就行了),后端生成AES的明文key,用明文key进行AES加密得到密文数据,用前端的公钥进行RSA加密得到密文key,API交互时并将密文数据与密文key进行传输,前端用自己的私钥进行RAS解密的到明文key,用明文key进行AES解密得到明文数据;前端给后端发送数据时同理,这样一来,传输的数据都是密文,且只有秘钥才能解密

      可惜这篇博客只提供了思路,但并没有具体的代码,我们在网上查找一下资料,开始生撸代码,实现一个前后端API交互数据加密——AES与RSA混合加密,并应用到项目中

      后端加、解密

      从网上查找工具类,再进行改造

      先引入Base64工具类

            <!-- Base64编码需要  -->
            <dependency>
                <groupId>org.apache.directory.studio</groupId>
                <artifactId>org.apache.commons.codec</artifactId>
                <version>1.8</version>
            </dependency>

      AES

    package cn.huanzi.ims.util;
    
    import org.apache.tomcat.util.codec.binary.Base64;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.security.SecureRandom;
    import java.util.Random;
    
    /**
     * AES加、解密算法工具类
     */
    public class AesUtil {
        /**
         * 加密算法AES
         */
        private static final String KEY_ALGORITHM = "AES";
    
        /**
         * key的长度,Wrong key size: must be equal to 128, 192 or 256
         * 传入时需要16、24、36
         */
        private static final Integer KEY_LENGTH = 16 * 8;
    
        /**
         * 算法名称/加密模式/数据填充方式
         * 默认:AES/ECB/PKCS5Padding
         */
        private static final String ALGORITHMS = "AES/ECB/PKCS5Padding";
    
        /**
         * 后端AES的key,由静态代码块赋值
         */
        public static String key;
    
        static {
            key = getKey();
        }
    
        /**
         * 获取key
         */
        public static String getKey() {
            StringBuilder uid = new StringBuilder();
            //产生16位的强随机数
            Random rd = new SecureRandom();
            for (int i = 0; i < KEY_LENGTH / 8; i++) {
                //产生0-2的3位随机数
                int type = rd.nextInt(3);
                switch (type) {
                    case 0:
                        //0-9的随机数
                        uid.append(rd.nextInt(10));
                        break;
                    case 1:
                        //ASCII在65-90之间为大写,获取大写随机
                        uid.append((char) (rd.nextInt(25) + 65));
                        break;
                    case 2:
                        //ASCII在97-122之间为小写,获取小写随机
                        uid.append((char) (rd.nextInt(25) + 97));
                        break;
                    default:
                        break;
                }
            }
            return uid.toString();
        }
    
        /**
         * 加密
         *
         * @param content    加密的字符串
         * @param encryptKey key值
         */
        public static String encrypt(String content, String encryptKey) throws Exception {
            //设置Cipher对象
            Cipher cipher = Cipher.getInstance(ALGORITHMS,new BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM));
    
            //调用doFinal
            byte[] b = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
    
            // 转base64
            return Base64.encodeBase64String(b);
    
        }
    
        /**
         * 解密
         *
         * @param encryptStr 解密的字符串
         * @param decryptKey 解密的key值
         */
        public static String decrypt(String encryptStr, String decryptKey) throws Exception {
            //base64格式的key字符串转byte
            byte[] decodeBase64 = Base64.decodeBase64(encryptStr);
    
            //设置Cipher对象
            Cipher cipher = Cipher.getInstance(ALGORITHMS,new BouncyCastleProvider());
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM));
    
            //调用doFinal解密
            byte[] decryptBytes = cipher.doFinal(decodeBase64);
            return new String(decryptBytes);
        }
    
    }
    
    AesUtil
    View Code

      RSA

    package cn.huanzi.ims.util;
    
    
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.Cipher;
    import java.io.ByteArrayOutputStream;
    import java.security.Key;
    import java.security.KeyFactory;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * RSA加、解密算法工具类
     */
    public class RsaUtil {
    
        /**
         * 加密算法AES
         */
        private static final String KEY_ALGORITHM = "RSA";
    
        /**
         * 算法名称/加密模式/数据填充方式
         * 默认:RSA/ECB/PKCS1Padding
         */
        private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding";
    
        /**
         * Map获取公钥的key
         */
        private static final String PUBLIC_KEY = "publicKey";
    
        /**
         * Map获取私钥的key
         */
        private static final String PRIVATE_KEY = "privateKey";
    
        /**
         * RSA最大加密明文大小
         */
        private static final int MAX_ENCRYPT_BLOCK = 117;
    
        /**
         * RSA最大解密密文大小
         */
        private static final int MAX_DECRYPT_BLOCK = 128;
    
        /**
         * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写:  245 256
         */
        private static final int INITIALIZE_LENGTH = 1024;
    
        /**
         * 后端RSA的密钥对(公钥和私钥)Map,由静态代码块赋值
         */
        private static Map<String, Object> genKeyPair = new HashMap<>();
    
        static {
            try {
                genKeyPair.putAll(genKeyPair());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 生成密钥对(公钥和私钥)
         */
        private static Map<String, Object> genKeyPair() throws Exception {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
            keyPairGen.initialize(INITIALIZE_LENGTH);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            Map<String, Object> keyMap = new HashMap<String, Object>(2);
            //公钥
            keyMap.put(PUBLIC_KEY, publicKey);
            //私钥
            keyMap.put(PRIVATE_KEY, privateKey);
            return keyMap;
        }
    
        /**
         * 私钥解密
         *
         * @param encryptedData 已加密数据
         * @param privateKey    私钥(BASE64编码)
         */
        public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
            //base64格式的key字符串转Key对象
            byte[] keyBytes = Base64.decodeBase64(privateKey);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
    
            //设置加密、填充方式
            /*
                如需使用更多加密、填充方式,引入
                <dependency>
                    <groupId>org.bouncycastle</groupId>
                    <artifactId>bcprov-jdk16</artifactId>
                    <version>1.46</version>
                </dependency>
                并改成
                Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
             */
            Cipher cipher = Cipher.getInstance(ALGORITHMS);
            cipher.init(Cipher.DECRYPT_MODE, privateK);
    
            //分段进行解密操作
            return encryptAndDecryptOfSubsection(encryptedData, cipher, MAX_DECRYPT_BLOCK);
        }
    
        /**
         * 公钥加密
         *
         * @param data      源数据
         * @param publicKey 公钥(BASE64编码)
         */
        public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
            //base64格式的key字符串转Key对象
            byte[] keyBytes = Base64.decodeBase64(publicKey);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicK = keyFactory.generatePublic(x509KeySpec);
    
            //设置加密、填充方式
            /*
                如需使用更多加密、填充方式,引入
                <dependency>
                    <groupId>org.bouncycastle</groupId>
                    <artifactId>bcprov-jdk16</artifactId>
                    <version>1.46</version>
                </dependency>
                并改成
                Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());
             */
            Cipher cipher = Cipher.getInstance(ALGORITHMS);
            cipher.init(Cipher.ENCRYPT_MODE, publicK);
    
            //分段进行加密操作
            return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK);
        }
    
        /**
         * 获取私钥
         */
        public static String getPrivateKey() {
            Key key = (Key) genKeyPair.get(PRIVATE_KEY);
            return Base64.encodeBase64String(key.getEncoded());
        }
    
        /**
         * 获取公钥
         */
        public static String getPublicKey() {
            Key key = (Key) genKeyPair.get(PUBLIC_KEY);
            return Base64.encodeBase64String(key.getEncoded());
        }
    
        /**
         * 分段进行加密、解密操作
         */
        private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception {
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > encryptBlock) {
                    cache = cipher.doFinal(data, offSet, encryptBlock);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * encryptBlock;
            }
            byte[] toByteArray = out.toByteArray();
            out.close();
            return toByteArray;
        }
    }
    
    RsaUtil
    View Code

      简单测试

      AES对称加密、解密简单测试

      1、字符串

    复制代码
       public static void main(String[] args) {
            //16位
            String key = "MIGfMA0GCSqGSIb3";
    
            //字符串
            String str = "huanzi.qch@qq.com:欢子";
            try {
                //加密
                String encrypt = AesUtil.encrypt(str, key);
                //解密
                String decrypt = AesUtil.decrypt(encrypt, key);
    
                System.out.println("加密前:" + str);
                System.out.println("加密后:" + encrypt);
                System.out.println("解密后:" + decrypt);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    复制代码
    加密前:huanzi.qch@qq.com:欢子
    加密后:dXPRtcdHPQSTwxLnmixkaSvNfGHhg5Gz8sGTtiqCpPo=
    解密后:huanzi.qch@qq.com:欢子

      2、复杂对象

    复制代码
        public static void main(String[] args) {
            //16位
            String key = "MIGfMA0GCSqGSIb3";
    
            //复杂对象
            ImsUserVo userVo = new ImsUserVo();
            userVo.setUserName("123456");
            userVo.setPassword("111111");
            try {
                //加密
                String encrypt = AesUtil.encrypt(userVo.toString(), key);
                //解密
                String decrypt = AesUtil.decrypt(encrypt, key);
    System.out.println("加密前:" + userVo.toString()); System.out.println("加密后:" + encrypt); System.out.println("解密后:" + decrypt); } catch (Exception e) { e.printStackTrace(); } }
    复制代码
    复制代码
    加密前:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
    加密后:AXv8ewfY+gbuZ/dCmGAxngLry+Idlp1NKZ8yyf9+bmrBggUBo3b+e4XRwMAE/DP+vFS2HpgeYQTrZM1ECjo01uvZ/T6lY7b2C6L8PTotYHQyJM3kOs+YNXL/uyvFZ2EICSQWhmM1XX+g0juHLCbgQDMNXc56S/7eH2p+su1+CTMygUBCF0U/gZaSzqylqujTb3sg7q4xMuxCQ6ne6xmL3ebjanOLeMJHypTDy1rlJTw=
    解密后:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
    复制代码

      RAS非对称加密、解密简单测试

      1、字符串的RSA公钥加密、私钥解密

    复制代码
        public static void main(String[] args) {
            //字符串
            String str = "huanzi.qch@qq.com:欢子";
            try {
                System.out.println("私钥:" + RsaUtil.getPrivateKey());
                System.out.println("公钥:" + RsaUtil.getPublicKey());
    
                //公钥加密
                byte[] ciphertext = RsaUtil.encryptByPublicKey(str.getBytes(), RsaUtil.getPublicKey());
                //私钥解密
                byte[] plaintext = RsaUtil.decryptByPrivateKey(ciphertext, RsaUtil.getPrivateKey());
    
                System.out.println("公钥加密前:" + str);
                System.out.println("公钥加密后:" + Base64.encodeBase64String(ciphertext));
                System.out.println("私钥解密后:" + new String(plaintext));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    复制代码
    复制代码
    私钥:MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANG08b2L0Hk1QCJXyTUI6A4CqW+KENCedZyJCYMteZ/vx93KeYZbPShhI3IWJJtj9U+ibiAVRjzmikI9lkKdgnCaOgTmEZis2RWgLzhcOpSqdp/J6d+YtmCD6UDeO3E6QPyfVv9d3qPrqaYUCxi7CmouzVaa/cJqrfYB7qGYt3u5AgMBAAECgYBbovQX3ebFcG2MFExKLpAovyUHJo/eeb/vHTrY5aBGMWNnGbks6uW4pWn1ypNIi8+AcvwobON6bUtxUrQ8e9OpUlDYTAAqDE8JvJoRC3theHpJbkHCdDLeNnz1EizUwxfe3X3IVwEYd29C00WXt0rUW2D/Fsa7ECp08taeV+ukAQJBAOyfO8opGp8t40bbyMRVsIR2zK19rN6Kd/NGvjjW/7BPgzDJZsybcN6e0AuhFaWTyHNSonpDztEQ0VWhF0mHokECQQDi4W9xCmzQf0l8mgXUP2IDY5YtQN9g9vL51qEwpcxhHxCCcid62R0y6T2GnRTmkEpSwPYZ2EZQrKtpGiEk4wt5AkAhwwqd6sWApuSB7MQ1t2BLVkQYERGEY0+AJ7zmkU7EUmQOpv4C/b7aFODsd9yF1pNIWScTuO8eh37G8AhJlo/BAkEAuIHfME3rGlA5whQ8I1T8b4cgjWLRhrit9tI+OiLLqDwsH/mX88b3gPy/pWa/pZW4a74zJeeFn3wc1heC1s2x+QJAXHVf9fZaFwDlD6nD3x0Sgu8Mdp8tsfdz2wIkvjtANc+eojkfxwdZd6PKWgmiPTLKNNqbPaLgtU74WVAnlpSgsw==
    公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRtPG9i9B5NUAiV8k1COgOAqlvihDQnnWciQmDLXmf78fdynmGWz0oYSNyFiSbY/VPom4gFUY85opCPZZCnYJwmjoE5hGYrNkVoC84XDqUqnafyenfmLZgg+lA3jtxOkD8n1b/Xd6j66mmFAsYuwpqLs1Wmv3Caq32Ae6hmLd7uQIDAQAB
    公钥加密前:huanzi.qch@qq.com:欢子
    公钥加密后:MQa65DyVZg/L8SBilLX1yUiajtiTBqUFpQ/qlrSRyMGCubylbp9KisowRghPxk9BuI3+ea/4QpidIZKJaZAbQQ+ZKyslSTk3nm6H+0BF9pMA7BUeC33xHSy+3lJrNOr5S+Vup1Oir3Nu8i2vJYQV1pPkB5+zyUVEcNLD3xr/eNQ=
    私钥解密后:huanzi.qch@qq.com:欢子
    复制代码

     

      2、复杂对象的RSA公钥加密、私钥解密

    复制代码
        public static void main(String[] args) {
            //复杂对象
            ImsUserVo userVo = new ImsUserVo();
            userVo.setUserName("123456");
            userVo.setPassword("111111");
            try {
                System.out.println("私钥:" + RsaUtil.getPrivateKey());
                System.out.println("公钥:" + RsaUtil.getPublicKey());
    
                //公钥加密
                byte[] ciphertext = RsaUtil.encryptByPublicKey(userVo.toString().getBytes(), RsaUtil.getPublicKey());
                //私钥解密
                byte[] plaintext = RsaUtil.decryptByPrivateKey(ciphertext, RsaUtil.getPrivateKey());
    
                System.out.println("公钥加密前:" + userVo.toString());
                System.out.println("公钥加密后:" + Base64.encodeBase64String(ciphertext));
                System.out.println("私钥解密后:" + new String(plaintext));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    复制代码
    复制代码
    私钥:MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAL6gSKs2G4iFrhPo0aLELfGzsCAaB5hztvclD9J2hZT2KXfs6S5JwZ0RWRR28rqHm0e2RNW3fzYyOLvSoq93n/TRAkmXBbVia3BCTrSzLPrKFY8JvLyXqbrV0NrxywY+4ZlgR5R+scWaj3LtUR63sSXb5ddmOg9XctrWBGvsKrNJAgMBAAECgYEAoql9OPPDzNxdbcnGUQDcP5pYGRx9DL75Cq2KccoHNNRVEGuNkp0HZLLv84GIoFikzS2gUUnyeFmkhck4X0hRqYpCo9DwRsBgBpqn+4ebjSu4bd3lG5KCAtMaPC5sAbznY1uuuJnUdul3p9PuF7AmFTsoFFB4YvstvkRna5ZPFA0CQQDfpxPYVpZjOsgng7187vEpFa9vlQxmyamvJ2iAeFLRHCqJwlq4VYqJkgr08SE1XCBqSVhXkLyIPAtdeqxU0iFLAkEA2jJfKVSy4I/BHmk/rdpw7InQ3ERBc/a09t2ZiI3bqtnobTIf/sMZEWPeMkY83RrWL9ZQvMNDa843cans3bm1OwJAIdipGi5QaAf3TnOTc5q9iFgtypcl31BZi5ZNLFQJRHgcv+hXzlmzs4oUemkbe3XLugoLgoT24y8jESyFc/iw7QJBAJdR26EENlF6IIoAn8Ln/Oxt30UCqQnNDE8v+2wyRSdFm+Uun/XEQ7xFsDDZeRg1pljinndqS3WWO+k92SEjy0UCQQCr5UsIMBAjpGCYXeXrRWYoYdfI6+R20I+uWoOGzoly+KK4ixqMLFuimEwrmXhYnJMzvVHfbLsoogBv9NOP9ffH
    公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+oEirNhuIha4T6NGixC3xs7AgGgeYc7b3JQ/SdoWU9il37OkuScGdEVkUdvK6h5tHtkTVt382Mji70qKvd5/00QJJlwW1YmtwQk60syz6yhWPCby8l6m61dDa8csGPuGZYEeUfrHFmo9y7VEet7El2+XXZjoPV3La1gRr7CqzSQIDAQAB
    公钥加密前:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
    公钥加密后:Un+1m/CbpzVkkxYrwNOWyEXqpsawxcdv4p3G+9b+SQRiC/THL8YG+IvqFCHnxizzYGB9LEvLbQxw72JB0Wlo1+/SvX7AJb2h0ddpvVUkPjmtXNo073SV1zMK+9NTCJUMMoHu/TIptxRbVxlBoGMHa+jq8h2y3RUOPtx/9zhBWlQmzZEifv0MjgAhKX5ucExYfXctcAVGHL959+TwKqKQmTENw5o0ElksaA0KIF+4L7RvpWVSqZT1Y4O2gMP9ALjamCx6ziRcmk4b4Q5Goph0nmw6nA387qVi3Vz6rQHrIpL0HT5OSiz1O7+2L3N0Him2IZeAgg3EZCi5xTGl54jGEw==
    私钥解密后:ImsUserVo(id=null, userName=123456, password=111111, nickName=null, gender=null, avatar=null, email=null, phone=null, sign=null, createdTime=null, updataTime=null)
    复制代码

      如需使用更多加密、填充方式,引入

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk16</artifactId>
        <version>1.46</version>
    </dependency>


      加解密的时候改成

    Cipher cipher = Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());

       前端加、解密

       AES我们采用CryptoJS,是一个标准和安全加密算法的JavaScript库,它的AES加密支持AES-128、AES-192和AES-256。下载或查看详情介绍请戳官网地址

      GitHub地址:https://github.com/brix/crypto-js

      官网地址:https://code.google.com/archive/p/crypto-js/

      RSA我们采用JSEncrypt,它是一个很好用的RSA加密算法的JavaScript库,使用PKCS#1进行填充,加解密使用方式很简单,具体的介绍或者下载请移步官网

      GitHub地址:https://github.com/travist/jsencrypt

      官网地址:http://travistidwell.com/jsencrypt/

      下载下来后我们在项目头部head.html引入,并新建两个小工具类

      

      AES

    /**
     * 简单封装一下
     */
    var aesUtil = {
    
        //获取key,
        genKey : function (length = 16) {
            let random = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            let str = "";
            for (let i = 0; i < length; i++) {
                str  = str + random.charAt(Math.random() * random.length)
            }
            return str;
        },
    
        //加密
        encrypt : function (plaintext,key) {
            if (plaintext instanceof Object) {
                //JSON.stringify
                plaintext = JSON.stringify(plaintext)
            }
            let encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(plaintext), CryptoJS.enc.Utf8.parse(key), {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
            return encrypted.toString();
        },
    
        //解密
        decrypt : function (ciphertext,key) {
            let decrypt = CryptoJS.AES.decrypt(ciphertext, CryptoJS.enc.Utf8.parse(key), {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
            let decString = CryptoJS.enc.Utf8.stringify(decrypt).toString();
            if(decString.charAt(0) === "{" || decString.charAt(0) === "[" ){
                //JSON.parse
                decString = JSON.parse(decString);
            }
            return decString;
        }
    };
    
    aesUtil
    View Code

      RSA

    /**
     * 简单封装一下
     */
    var rsaUtil = {
        //RSA 位数,这里要跟后端对应
        bits: 1024,
    
        //当前JSEncrypted对象
        thisKeyPair: {},
    
        //生成密钥对(公钥和私钥)
        genKeyPair: function (bits = rsaUtil.bits) {
            let genKeyPair = {};
            rsaUtil.thisKeyPair = new JSEncrypt({default_key_size: bits});
    
            //获取私钥
            genKeyPair.privateKey = rsaUtil.thisKeyPair.getPrivateKey();
    
            //获取公钥
            genKeyPair.publicKey = rsaUtil.thisKeyPair.getPublicKey();
    
            return genKeyPair;
        },
    
        //公钥加密
        encrypt: function (plaintext, publicKey) {
            if (plaintext instanceof Object) {
                //1、JSON.stringify
                plaintext = JSON.stringify(plaintext)
            }
            publicKey && rsaUtil.thisKeyPair.setPublicKey(publicKey);
            return rsaUtil.thisKeyPair.encrypt(JSON.stringify(plaintext));
        },
    
        //私钥解密
        decrypt: function (ciphertext, privateKey) {
            privateKey && rsaUtil.thisKeyPair.setPrivateKey(privateKey);
            let decString = rsaUtil.thisKeyPair.decrypt(ciphertext);
            if(decString.charAt(0) === "{" || decString.charAt(0) === "[" ){
                //JSON.parse
                decString = JSON.parse(decString);
            }
            return decString;
        }
    };
    
    rsaUtil
    View Code

      简单测试

      AES对称加密、解密简单测试

      1、字符串

    复制代码
    //字符串
    let text = "huanzi.qch@qq.com:欢子";
    
    //key
    let genKey = aesUtil.genKey();
    //key加密
    let ciphertext = aesUtil.encrypt(text,genKey);
    //key解密
    let plaintext = aesUtil.decrypt(ciphertext,genKey);
    
    console.log("key:");console.log(genKey);
    console.log("加密前:");console.log(text);
    console.log("key加密后:" + ciphertext);
    console.log("key解密后:");console.log(plaintext);
    复制代码
    key:q99IsnEuryk1ZvgX
    加密前:huanzi.qch@qq.com:欢子
    key加密后:aZn58GtEj9Is0hNWbJoqpRD6RkiBVPCHOvva3Xq2PYo=
    key解密后:huanzi.qch@qq.com:欢子

      2、复杂对象

    复制代码
    //复杂对象
    let user = {username: "欢子", password: 123456, remark: "abcd!@#$:"};
    
    //key
    let genKey = aesUtil.genKey();
    //key加密
    let ciphertext = aesUtil.encrypt(user,genKey);
    //key解密
    let plaintext = aesUtil.decrypt(ciphertext,genKey);
    
    console.log("key:");console.log(genKey);
    console.log("加密前:");console.log(user);
    console.log("key加密后:" + ciphertext);
    console.log("key解密后:");console.log(plaintext);
    复制代码
    key:e6gzizHIpDfc6hbg
    加密前:{username: "欢子", password: 123456, remark: "abcd!@#$:"}
    key加密后:YdNw5AwteEp8WZs5xMv0YiGcXvX81P9MCLOvroHjfLUyQV/GwJ6obRqi4DT2ucJy8DWrKueOzLGLSQXUVhAgIA==
    key解密后:{username: "欢子", password: 123456, remark: "abcd!@#$:"}

      RAS非对称加密、解密简单测试

      1、字符串的RSA公钥加密、私钥解密

    复制代码
    //普通字符串
    let text = "huanzi.qch@qq.com:欢子";
    
    //秘钥对
    let keyPair = rsaUtil.genKeyPair();
    //公钥加密
    let ciphertext = rsaUtil.encrypt(text,keyPair.publicKey);
    //私钥解密
    let plaintext = rsaUtil.decrypt(ciphertext,keyPair.privateKey);
    
    console.log("秘钥:");console.log(keyPair.privateKey);
    console.log("公钥:" + keyPair.publicKey);
    console.log("加密前:" + text);
    console.log("公钥加密后:" + ciphertext);
    console.log("解密后:" + plaintext);
    复制代码
    复制代码
    秘钥:MIICXQIBAAKBgQDtBKNg9NJ0+mMWq+99geoi32t+xkbJuvQ4Wr7x8I+zGT8xiG+jG+OAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP+6/9iOLMfe4cHtGq+kdX7QPx4uabnIXAREnR4nVl5Mtxf+vEHXGmEPwIDAQABAoGBANH92gJ85jld3YyoqHa6M4bSC5s2cGEqklWbkLEqQSacp7BrAP2yJ85UPkB9oRtYbr0tkciLYnptshq03TR2r7QT5+ovb5KJ2MQExTXk8GZTO/5sSqD0zwA9SESlAmWj8yc49p5Tk0h5UYFgsRATTer1n1llziryXa4QMiIfKrmBAkEA+za3DryZnMyNqre6Kx+FYsFVvIbHRU7tJ5LOiZ43Vl0DXq44zmeNQeh6MzfH6sc0Avu1c61/+KNDVf23yfVt7QJBAPGIr0GokOZ+L0sttiEoQSq/dBdYaSCBfTht+rA/9ie8RgcFJkYj4h/6RPzdYIRWDco5RzI+oPnZmFC4rfPjFVsCQHE2XFMw3c2TRfj86dKLVxKFbL0UxHNQuYIPIDNW8TtjmaQuwf0LH9bnDUNNzTPaaG87vq+OLlEAStVTDWPfzpUCQDVJvbjTstxXfKGufR9FnVMMGFXKOK9mQjU/9m4KPom3vQ9xcGdLJWl+stfDE7c+sR4rkuyf6q4U9sjgZeiH8j8CQQCfaxXfxiDJPztUDm1AKI6uDwz4P4eiYRbbbQ5x+iQSunHbq0Y7U9UUkWcLw0xDhReHEYkFuOeiBj2ViAPJz1r0
    公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDtBKNg9NJ0+mMWq+99geoi32t+xkbJuvQ4Wr7x8I+zGT8xiG+jG+OAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP+6/9iOLMfe4cHtGq+kdX7QPx4uabnIXAREnR4nVl5Mtxf+vEHXGmEPwIDAQAB
    加密前:huanzi.qch@qq.com:欢子
    公钥加密后:aUkUMYC7lF8M1xzcx5ZEdc0DQt4FrqvWqEnD30raV++j7rwsfEcyXpPmeF1g2LR86FVG3oxgdTptorkwUDSXB3Tv4av7toGg7Zcf9l1vs5WQX7kCDTitwBVwyBNTZq22xed1J/LAkDujDav6tUJHdMmRKYVe2NeTswvWLOqWWW4=
    解密后:"huanzi.qch@qq.com:欢子"
    复制代码

      2、复杂对象的RSA公钥加密、私钥解密

    复制代码
    //复杂对象
    let user = {username:"欢子",password:123456,remark:"abcd!@#$:"};
    
    //秘钥对
    let keyPair = rsaUtil.genKeyPair();
    //公钥加密
    let ciphertext = rsaUtil.encrypt(user,keyPair.publicKey);
    //私钥解密
    let plaintext = rsaUtil.decrypt(ciphertext,keyPair.privateKey);
    
    console.log("秘钥:");console.log(keyPair.privateKey);
    console.log("公钥:" + keyPair.publicKey);
    console.log("加密前:" + user);
    console.log("公钥加密后:" + ciphertext);
    console.log("解密后:" + plaintext);
    复制代码
    复制代码
    秘钥:MIICXAIBAAKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUM+zLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQABAoGAbwrLhkIvjk938nNnaRufoqqrW+5OMrzgis6bWlghckawr6NPj5ZPs7nKF/Sv79jdA/N55I6V7bHrxI2N+S9Ckm2ygv8nNYimSjzspR48SqVRuH/xYmQQ9hi8Iy4dTlCMud34oXsV2sYI5tEn7f3bypOVfJa6kHSqxe1PIQTxirkCQQDjmHOp2JMcltpL+639nnNgZ2U06cRhPHX+tcGTIgoqu1Sqp0bKH7QuUF9WPNWxHrbYY5+s5jnnhTTwQZg74atTAkEAwXelo0JLYHpML7+sLs8aUzitRJXjkW3dY4JPf1wLTNLbawvi4KA/6NA3jx7kCD6KzM7vsWWsRgArrUWa1Dbn6wJARY5pAuZyh1E/I+umEBWl0zemQZaT8tekhBSONWY4zzhzNrhqtQkdau4bROLQuBHX9af0u8WcuroGJMsXOG3OiwJBALc91OPJ8cziaPC80Z/QRvXV877HXTCsZ4lNrnBJxOYxvOMp8eyhu4aOWGE1d/QbEKolwj86tq3ikXvfNmOT0ZsCQBkfviB7CKdHCrUCnpAK0upa9x8uFraomDNxFP/HwTFPSOPGhA5pgAzJgygSu2hpEwFFIwfC3E+pQ2EAhoeIcdw=
    公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUM+zLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQAB
    加密前:[object Object]
    公钥加密后:LwggD8SIeWoqzh4gHh/vJ9nEZsqeNZfoxgrRRPD7k0wpp9/uZmR5kfRJ8O59yW5IaOt1z50mJ26ylRBOKNoTTl7Rt4zVmBYX4EXr4Ajq3CINFcPI/j5l8yQRSIgLPUvOxhIAKmfrgNKCaLjSdjK/CnTbPrZoDArI8iAHq/ih4r8=
    解密后:"{"username":"欢子","password":123456,"remark":"abcd!@#$:"}"
    复制代码

      联调测试

      自己解密自己加密的的数据基本上没有什么问题,重要的是解密对方加密的数据会不会成功,前后端相互加解密的工程中,最重要的就是保持两边的加密、填充方式一致、加密位数一致,还有就是后端Base64字符串转成byte[]数组的时候要注意,Base64工具类转跟直接字符串getByte()跟用输入输出流来转,得到的数组结果有差异,在本次测试中我也是搞了好久才使得前后端一致,紧跟上面的简单测试,接下来我们进行前后端联调测试

      1、AES:前后端相互用对方的key解密对方加密的数据

      2、RSA:前后端相互用对方的公钥进行加密数据,然后将数据叫给对方解密

      这里要讲一下步骤,不然大家看不懂下面这几张图,为了确保后端加密解密用的是同一个密钥对,我们采用控制台输入前端秘钥跟前端使用后端公钥加密后的密文,然后再使用私钥去解密从而得到前端的明文,而js前端部分,只有不刷新页面,对象数据会存在浏览器内存中,确保了加密解密是用同一个对密钥对

      总而言之,测试结果是正确的,接下来就可以再项目中进行加解密了

       项目应用

      理论思路

      前、后端的代码都封装好了,并且都通过了简单测试,接下来就是应用到项目中,首先我们要解决的是生成公钥秘钥并交换的问题,思路如下:

      生成:

        1、后端:在项目启动的时候生成RSA公钥秘钥并在整个项目运行中不发生改变(或者每隔一段时间更新一次也行),AES的key是每次响应之前随机获取

        2、前端:我们在访问页面开始生成RSA公钥秘钥并且希望页面在刷新之前都不发生改变,因此将它们存在window对象中(如果需要更加健全,使用H5的本地存储localStorage、sessionStorage)在head.html中生成,AES的key在每次发起请求之前随机获取

      交换:

        1、前端获取后端RSA公钥:前端访问登录页面(网站入口),后台返回modelAndView时注入RSA的公钥,前端获取用存到sessionStorage中,直到回话关闭

        2、后端获取前端RSA公钥:前端公钥跟随http请求发送到后端

      生成与交换公钥的问题解决了,接下来就是如何传输AES加密后的数据跟RSA公钥加密后的AES的key,思路如下:

      1、前端:重写$.ajax方法(或者封装一个ajax),发送数据前用AES加密数据(key随机生成),用后端的RSA公钥加密AES的key,将加密后的data数据、加密后的AES的key、前端RSA公钥发送到后端;触发回调后,先用前端RSA私钥解密AES的key,在用明文key去解密

      2、后端:写两个自定义注解Encrypt、Decrypt,AOP拦截所有带自定义注解的post请求进行加密解密,有@Encrypt需要对返回值进行加密,有@Decrypt需要对参数进行解密,加密解密过程与前端的操作同理

      前端代码

      引入js

            <!--CryptoJS jsencrypt -->
            <script th:src="@{/js/cryptojs.js}"></script>
            <script th:src="@{/js/jsencrypt.js}"></script>
            <script th:src="@{/js/aesUtil.js}"></script>
            <script th:src="@{/js/rsaUtil.js}"></script>

      下载CryptoJs跟jsencrypt下来发现CryptoJs需要引入很多js,因此在网上找了这个整合的js,引它就够了

    !function(t,n){"object"==typeof exports?module.exports=exports=n():"function"==typeof define&&define.amd?define([],n):t.CryptoJS=n()}(this,function(){var t=t||function(t,n){var i=Object.create||function(){function t(){}return function(n){var i;return t.prototype=n,i=new t,t.prototype=null,i}}(),e={},r=e.lib={},o=r.Base=function(){return{extend:function(t){var n=i(this);return t&&n.mixIn(t),n.hasOwnProperty("init")&&this.init!==n.init||(n.init=function(){n.$super.init.apply(this,arguments)}),n.init.prototype=n,n.$super=this,n},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var n in t)t.hasOwnProperty(n)&&(this[n]=t[n]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),s=r.WordArray=o.extend({init:function(t,i){t=this.words=t||[],i!=n?this.sigBytes=i:this.sigBytes=4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var n=this.words,i=t.words,e=this.sigBytes,r=t.sigBytes;if(this.clamp(),e%4)for(var o=0;o<r;o++){var s=i[o>>>2]>>>24-o%4*8&255;n[e+o>>>2]|=s<<24-(e+o)%4*8}else for(var o=0;o<r;o+=4)n[e+o>>>2]=i[o>>>2];return this.sigBytes+=r,this},clamp:function(){var n=this.words,i=this.sigBytes;n[i>>>2]&=4294967295<<32-i%4*8,n.length=t.ceil(i/4)},clone:function(){var t=o.clone.call(this);return t.words=this.words.slice(0),t},random:function(n){for(var i,e=[],r=function(n){var n=n,i=987654321,e=4294967295;return function(){i=36969*(65535&i)+(i>>16)&e,n=18e3*(65535&n)+(n>>16)&e;var r=(i<<16)+n&e;return r/=4294967296,r+=.5,r*(t.random()>.5?1:-1)}},o=0;o<n;o+=4){var a=r(4294967296*(i||t.random()));i=987654071*a(),e.push(4294967296*a()|0)}return new s.init(e,n)}}),a=e.enc={},c=a.Hex={stringify:function(t){for(var n=t.words,i=t.sigBytes,e=[],r=0;r<i;r++){var o=n[r>>>2]>>>24-r%4*8&255;e.push((o>>>4).toString(16)),e.push((15&o).toString(16))}return e.join("")},parse:function(t){for(var n=t.length,i=[],e=0;e<n;e+=2)i[e>>>3]|=parseInt(t.substr(e,2),16)<<24-e%8*4;return new s.init(i,n/2)}},u=a.Latin1={stringify:function(t){for(var n=t.words,i=t.sigBytes,e=[],r=0;r<i;r++){var o=n[r>>>2]>>>24-r%4*8&255;e.push(String.fromCharCode(o))}return e.join("")},parse:function(t){for(var n=t.length,i=[],e=0;e<n;e++)i[e>>>2]|=(255&t.charCodeAt(e))<<24-e%4*8;return new s.init(i,n)}},f=a.Utf8={stringify:function(t){try{return decodeURIComponent(escape(u.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return u.parse(unescape(encodeURIComponent(t)))}},h=r.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new s.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(n){var i=this._data,e=i.words,r=i.sigBytes,o=this.blockSize,a=4*o,c=r/a;c=n?t.ceil(c):t.max((0|c)-this._minBufferSize,0);var u=c*o,f=t.min(4*u,r);if(u){for(var h=0;h<u;h+=o)this._doProcessBlock(e,h);var p=e.splice(0,u);i.sigBytes-=f}return new s.init(p,f)},clone:function(){var t=o.clone.call(this);return t._data=this._data.clone(),t},_minBufferSize:0}),p=(r.Hasher=h.extend({cfg:o.extend(),init:function(t){this.cfg=this.cfg.extend(t),this.reset()},reset:function(){h.reset.call(this),this._doReset()},update:function(t){return this._append(t),this._process(),this},finalize:function(t){t&&this._append(t);var n=this._doFinalize();return n},blockSize:16,_createHelper:function(t){return function(n,i){return new t.init(i).finalize(n)}},_createHmacHelper:function(t){return function(n,i){return new p.HMAC.init(t,i).finalize(n)}}}),e.algo={});return e}(Math);return t});
    //# sourceMappingURL=core.min.js.map
    !function(e,t,i){"object"==typeof exports?module.exports=exports=t(require("./core.min"),require("./sha1.min"),require("./hmac.min")):"function"==typeof define&&define.amd?define(["./core.min","./sha1.min","./hmac.min"],t):t(e.CryptoJS)}(this,function(e){return function(){var t=e,i=t.lib,r=i.Base,n=i.WordArray,o=t.algo,a=o.MD5,c=o.EvpKDF=r.extend({cfg:r.extend({keySize:4,hasher:a,iterations:1}),init:function(e){this.cfg=this.cfg.extend(e)},compute:function(e,t){for(var i=this.cfg,r=i.hasher.create(),o=n.create(),a=o.words,c=i.keySize,f=i.iterations;a.length<c;){s&&r.update(s);var s=r.update(e).finalize(t);r.reset();for(var u=1;u<f;u++)s=r.finalize(s),r.reset();o.concat(s)}return o.sigBytes=4*c,o}});t.EvpKDF=function(e,t,i){return c.create(i).compute(e,t)}}(),e.EvpKDF});
    //# sourceMappingURL=evpkdf.min.js.map
    !function(r,e){"object"==typeof exports?module.exports=exports=e(require("./core.min")):"function"==typeof define&&define.amd?define(["./core.min"],e):e(r.CryptoJS)}(this,function(r){return function(){function e(r,e,t){for(var n=[],i=0,o=0;o<e;o++)if(o%4){var f=t[r.charCodeAt(o-1)]<<o%4*2,c=t[r.charCodeAt(o)]>>>6-o%4*2;n[i>>>2]|=(f|c)<<24-i%4*8,i++}return a.create(n,i)}var t=r,n=t.lib,a=n.WordArray,i=t.enc;i.Base64={stringify:function(r){var e=r.words,t=r.sigBytes,n=this._map;r.clamp();for(var a=[],i=0;i<t;i+=3)for(var o=e[i>>>2]>>>24-i%4*8&255,f=e[i+1>>>2]>>>24-(i+1)%4*8&255,c=e[i+2>>>2]>>>24-(i+2)%4*8&255,s=o<<16|f<<8|c,h=0;h<4&&i+.75*h<t;h++)a.push(n.charAt(s>>>6*(3-h)&63));var p=n.charAt(64);if(p)for(;a.length%4;)a.push(p);return a.join("")},parse:function(r){var t=r.length,n=this._map,a=this._reverseMap;if(!a){a=this._reverseMap=[];for(var i=0;i<n.length;i++)a[n.charCodeAt(i)]=i}var o=n.charAt(64);if(o){var f=r.indexOf(o);f!==-1&&(t=f)}return e(r,t,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}}(),r.enc.Base64});
    //# sourceMappingURL=enc-base64.min.js.map
    !function(e,t,r){"object"==typeof exports?module.exports=exports=t(require("./core.min"),require("./evpkdf.min")):"function"==typeof define&&define.amd?define(["./core.min","./evpkdf.min"],t):t(e.CryptoJS)}(this,function(e){e.lib.Cipher||function(t){var r=e,i=r.lib,n=i.Base,c=i.WordArray,o=i.BufferedBlockAlgorithm,s=r.enc,a=(s.Utf8,s.Base64),f=r.algo,p=f.EvpKDF,d=i.Cipher=o.extend({cfg:n.extend(),createEncryptor:function(e,t){return this.create(this._ENC_XFORM_MODE,e,t)},createDecryptor:function(e,t){return this.create(this._DEC_XFORM_MODE,e,t)},init:function(e,t,r){this.cfg=this.cfg.extend(r),this._xformMode=e,this._key=t,this.reset()},reset:function(){o.reset.call(this),this._doReset()},process:function(e){return this._append(e),this._process()},finalize:function(e){e&&this._append(e);var t=this._doFinalize();return t},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){function e(e){return"string"==typeof e?B:x}return function(t){return{encrypt:function(r,i,n){return e(i).encrypt(t,r,i,n)},decrypt:function(r,i,n){return e(i).decrypt(t,r,i,n)}}}}()}),h=(i.StreamCipher=d.extend({_doFinalize:function(){var e=this._process(!0);return e},blockSize:1}),r.mode={}),u=i.BlockCipherMode=n.extend({createEncryptor:function(e,t){return this.Encryptor.create(e,t)},createDecryptor:function(e,t){return this.Decryptor.create(e,t)},init:function(e,t){this._cipher=e,this._iv=t}}),l=h.CBC=function(){function e(e,r,i){var n=this._iv;if(n){var c=n;this._iv=t}else var c=this._prevBlock;for(var o=0;o<i;o++)e[r+o]^=c[o]}var r=u.extend();return r.Encryptor=r.extend({processBlock:function(t,r){var i=this._cipher,n=i.blockSize;e.call(this,t,r,n),i.encryptBlock(t,r),this._prevBlock=t.slice(r,r+n)}}),r.Decryptor=r.extend({processBlock:function(t,r){var i=this._cipher,n=i.blockSize,c=t.slice(r,r+n);i.decryptBlock(t,r),e.call(this,t,r,n),this._prevBlock=c}}),r}(),_=r.pad={},v=_.Pkcs7={pad:function(e,t){for(var r=4*t,i=r-e.sigBytes%r,n=i<<24|i<<16|i<<8|i,o=[],s=0;s<i;s+=4)o.push(n);var a=c.create(o,i);e.concat(a)},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},y=(i.BlockCipher=d.extend({cfg:d.cfg.extend({mode:l,padding:v}),reset:function(){d.reset.call(this);var e=this.cfg,t=e.iv,r=e.mode;if(this._xformMode==this._ENC_XFORM_MODE)var i=r.createEncryptor;else{var i=r.createDecryptor;this._minBufferSize=1}this._mode&&this._mode.__creator==i?this._mode.init(this,t&&t.words):(this._mode=i.call(r,this,t&&t.words),this._mode.__creator=i)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var e=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){e.pad(this._data,this.blockSize);var t=this._process(!0)}else{var t=this._process(!0);e.unpad(t)}return t},blockSize:4}),i.CipherParams=n.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}})),m=r.format={},k=m.OpenSSL={stringify:function(e){var t=e.ciphertext,r=e.salt;if(r)var i=c.create([1398893684,1701076831]).concat(r).concat(t);else var i=t;return i.toString(a)},parse:function(e){var t=a.parse(e),r=t.words;if(1398893684==r[0]&&1701076831==r[1]){var i=c.create(r.slice(2,4));r.splice(0,4),t.sigBytes-=16}return y.create({ciphertext:t,salt:i})}},x=i.SerializableCipher=n.extend({cfg:n.extend({format:k}),encrypt:function(e,t,r,i){i=this.cfg.extend(i);var n=e.createEncryptor(r,i),c=n.finalize(t),o=n.cfg;return y.create({ciphertext:c,key:r,iv:o.iv,algorithm:e,mode:o.mode,padding:o.padding,blockSize:e.blockSize,formatter:i.format})},decrypt:function(e,t,r,i){i=this.cfg.extend(i),t=this._parse(t,i.format);var n=e.createDecryptor(r,i).finalize(t.ciphertext);return n},_parse:function(e,t){return"string"==typeof e?t.parse(e,this):e}}),g=r.kdf={},S=g.OpenSSL={execute:function(e,t,r,i){i||(i=c.random(8));var n=p.create({keySize:t+r}).compute(e,i),o=c.create(n.words.slice(t),4*r);return n.sigBytes=4*t,y.create({key:n,iv:o,salt:i})}},B=i.PasswordBasedCipher=x.extend({cfg:x.cfg.extend({kdf:S}),encrypt:function(e,t,r,i){i=this.cfg.extend(i);var n=i.kdf.execute(r,e.keySize,e.ivSize);i.iv=n.iv;var c=x.encrypt.call(this,e,t,n.key,i);return c.mixIn(n),c},decrypt:function(e,t,r,i){i=this.cfg.extend(i),t=this._parse(t,i.format);var n=i.kdf.execute(r,e.keySize,e.ivSize,t.salt);i.iv=n.iv;var c=x.decrypt.call(this,e,t,n.key,i);return c}})}()});
    //# sourceMappingURL=cipher-core.min.js.map
    !function(e,i){"object"==typeof exports?module.exports=exports=i(require("./core.min")):"function"==typeof define&&define.amd?define(["./core.min"],i):i(e.CryptoJS)}(this,function(e){!function(){var i=e,t=i.lib,n=t.Base,s=i.enc,r=s.Utf8,o=i.algo;o.HMAC=n.extend({init:function(e,i){e=this._hasher=new e.init,"string"==typeof i&&(i=r.parse(i));var t=e.blockSize,n=4*t;i.sigBytes>n&&(i=e.finalize(i)),i.clamp();for(var s=this._oKey=i.clone(),o=this._iKey=i.clone(),a=s.words,f=o.words,c=0;c<t;c++)a[c]^=1549556828,f[c]^=909522486;s.sigBytes=o.sigBytes=n,this.reset()},reset:function(){var e=this._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var i=this._hasher,t=i.finalize(e);i.reset();var n=i.finalize(this._oKey.clone().concat(t));return n}})}()});
    //# sourceMappingURL=hmac.min.js.map
    !function(e,o,r){"object"==typeof exports?module.exports=exports=o(require("./core.min"),require("./cipher-core.min")):"function"==typeof define&&define.amd?define(["./core.min","./cipher-core.min"],o):o(e.CryptoJS)}(this,function(e){return e.mode.ECB=function(){var o=e.lib.BlockCipherMode.extend();return o.Encryptor=o.extend({processBlock:function(e,o){this._cipher.encryptBlock(e,o)}}),o.Decryptor=o.extend({processBlock:function(e,o){this._cipher.decryptBlock(e,o)}}),o}(),e.mode.ECB});
    //# sourceMappingURL=mode-ecb.min.js.map
    !function(e,r,i){"object"==typeof exports?module.exports=exports=r(require("./core.min"),require("./cipher-core.min")):"function"==typeof define&&define.amd?define(["./core.min","./cipher-core.min"],r):r(e.CryptoJS)}(this,function(e){return e.pad.Pkcs7});
    //# sourceMappingURL=pad-pkcs7.min.js.map
    !function(e,r,i){"object"==typeof exports?module.exports=exports=r(require("./core.min"),require("./enc-base64.min"),require("./md5.min"),require("./evpkdf.min"),require("./cipher-core.min")):"function"==typeof define&&define.amd?define(["./core.min","./enc-base64.min","./md5.min","./evpkdf.min","./cipher-core.min"],r):r(e.CryptoJS)}(this,function(e){return function(){var r=e,i=r.lib,n=i.BlockCipher,o=r.algo,t=[],c=[],s=[],f=[],a=[],d=[],u=[],v=[],h=[],y=[];!function(){for(var e=[],r=0;r<256;r++)r<128?e[r]=r<<1:e[r]=r<<1^283;for(var i=0,n=0,r=0;r<256;r++){var o=n^n<<1^n<<2^n<<3^n<<4;o=o>>>8^255&o^99,t[i]=o,c[o]=i;var p=e[i],l=e[p],_=e[l],k=257*e[o]^16843008*o;s[i]=k<<24|k>>>8,f[i]=k<<16|k>>>16,a[i]=k<<8|k>>>24,d[i]=k;var k=16843009*_^65537*l^257*p^16843008*i;u[o]=k<<24|k>>>8,v[o]=k<<16|k>>>16,h[o]=k<<8|k>>>24,y[o]=k,i?(i=p^e[e[e[_^p]]],n^=e[e[n]]):i=n=1}}();var p=[0,1,2,4,8,16,32,64,128,27,54],l=o.AES=n.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var e=this._keyPriorReset=this._key,r=e.words,i=e.sigBytes/4,n=this._nRounds=i+6,o=4*(n+1),c=this._keySchedule=[],s=0;s<o;s++)if(s<i)c[s]=r[s];else{var f=c[s-1];s%i?i>6&&s%i==4&&(f=t[f>>>24]<<24|t[f>>>16&255]<<16|t[f>>>8&255]<<8|t[255&f]):(f=f<<8|f>>>24,f=t[f>>>24]<<24|t[f>>>16&255]<<16|t[f>>>8&255]<<8|t[255&f],f^=p[s/i|0]<<24),c[s]=c[s-i]^f}for(var a=this._invKeySchedule=[],d=0;d<o;d++){var s=o-d;if(d%4)var f=c[s];else var f=c[s-4];d<4||s<=4?a[d]=f:a[d]=u[t[f>>>24]]^v[t[f>>>16&255]]^h[t[f>>>8&255]]^y[t[255&f]]}}},encryptBlock:function(e,r){this._doCryptBlock(e,r,this._keySchedule,s,f,a,d,t)},decryptBlock:function(e,r){var i=e[r+1];e[r+1]=e[r+3],e[r+3]=i,this._doCryptBlock(e,r,this._invKeySchedule,u,v,h,y,c);var i=e[r+1];e[r+1]=e[r+3],e[r+3]=i},_doCryptBlock:function(e,r,i,n,o,t,c,s){for(var f=this._nRounds,a=e[r]^i[0],d=e[r+1]^i[1],u=e[r+2]^i[2],v=e[r+3]^i[3],h=4,y=1;y<f;y++){var p=n[a>>>24]^o[d>>>16&255]^t[u>>>8&255]^c[255&v]^i[h++],l=n[d>>>24]^o[u>>>16&255]^t[v>>>8&255]^c[255&a]^i[h++],_=n[u>>>24]^o[v>>>16&255]^t[a>>>8&255]^c[255&d]^i[h++],k=n[v>>>24]^o[a>>>16&255]^t[d>>>8&255]^c[255&u]^i[h++];a=p,d=l,u=_,v=k}var p=(s[a>>>24]<<24|s[d>>>16&255]<<16|s[u>>>8&255]<<8|s[255&v])^i[h++],l=(s[d>>>24]<<24|s[u>>>16&255]<<16|s[v>>>8&255]<<8|s[255&a])^i[h++],_=(s[u>>>24]<<24|s[v>>>16&255]<<16|s[a>>>8&255]<<8|s[255&d])^i[h++],k=(s[v>>>24]<<24|s[a>>>16&255]<<16|s[d>>>8&255]<<8|s[255&u])^i[h++];e[r]=p,e[r+1]=l,e[r+2]=_,e[r+3]=k},keySize:8});r.AES=n._createHelper(l)}(),e.AES});
    //# sourceMappingURL=aes.min.js.map
    !function(e,n){"object"==typeof exports?module.exports=exports=n(require("./core.min")):"function"==typeof define&&define.amd?define(["./core.min"],n):n(e.CryptoJS)}(this,function(e){return e.enc.Utf8});
    //# sourceMappingURL=enc-utf8.min.js.map
    
    cryptojs.js
    View Code

      获取后端公钥

    <script th:inline="javascript">
        //获取后端RSA公钥并存到sessionStorage
        sessionStorage.setItem('javaPublicKey', [[${publicKey}]]);
    </script>

      重写ajax以及生成前端密钥对,因为我们项目中大部分都是使用$.ajax方法,所有重写它比较合适,并且我们只拦截post请求

    <script>
                //获取前端RSA公钥密码、AES的key,并放到window
                let genKeyPair = rsaUtil.genKeyPair();
                window.jsPublicKey = genKeyPair.publicKey;
                window.jsPrivateKey = genKeyPair.privateKey;
    
                /**
                 * 重写jquery的ajax方法
                 */
                let _ajax = $.ajax;//首先备份下jquery的ajax方法
                $.ajax = function (opt) {
                    //备份opt中error和success方法
                    let fn = {
                        error: function (XMLHttpRequest, textStatus, errorThrown) {
                        },
                        success: function (data, textStatus) {
                        }
                    };
                    if (opt.error) {
                        fn.error = opt.error;
                    }
                    if (opt.success) {
                        fn.success = opt.success;
                    }
    
                    //加密再传输
                    if (opt.type.toLowerCase() === "post") {
                        let data = opt.data;
                        //发送请求之前随机获取AES的key
                        let aesKey = aesUtil.genKey();
                        data = {
                            data: aesUtil.encrypt(data, aesKey),//AES加密后的数据
                            aesKey: rsaUtil.encrypt(aesKey, sessionStorage.getItem('javaPublicKey')),//后端RSA公钥加密后的AES的key
                            publicKey: window.jsPublicKey//前端公钥
                        };
                        opt.data = data;
                    }
    
                    //扩展增强处理
                    let _opt = $.extend(opt, {
                        //成功回调方法增强处理
                        success: function (data, textStatus) {
                            if (opt.type.toLowerCase() === "post") {
                                data = aesUtil.decrypt(data.data.data, rsaUtil.decrypt(data.data.aesKey, window.jsPrivateKey));
                            }
                            //先获取明文aesKey,再用明文key去解密数据
                            fn.success(data, textStatus);
                        }
                    });
                    return _ajax(_opt);
                };
            </script>
    
    head.html
    View Code

      后端代码

      两个自定义注解

    package cn.huanzi.ims.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Decrypt {
    
    }
    
    Decrypt
    View Code
    package cn.huanzi.ims.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Encrypt {
    
    }
    
    Encrypt
    View Code

      

    aop扫描所有的controller,拦截带自定义标签的post请求,进行解密、加密再将明文设置回去(如何使用aop?请戳我之前的博客:SpringBoot系列——aop 面向切面

      要注意的是:

      1、我们在aop只设置了第一个参数,因此controller方法需要是实体接参且第一个参数就是,所有要求,要么有一个实体Vo参数,要么没有参数;

      2、对于返回值,需要是统一的返回值,因为我们目前是按统一的返回值设置值的,例如本例中的Result,是我们约定好的统一返回值(后续升级可以用反射来设置值);

      3、还有一个需要注意的地方,method方法必须是要public修饰的才能设置方法的形参值,private的设置不了;

      PS:2019-06-12补充,我们之前在进行jackson序列化和反序列化忘记对date进行处理,导致时间格式错乱,现在补充一下

    package cn.huanzi.ims.aspect;
    
    import cn.huanzi.ims.annotation.Decrypt;
    import cn.huanzi.ims.annotation.Encrypt;
    import cn.huanzi.ims.common.pojo.Result;
    import cn.huanzi.ims.util.AesUtil;
    import cn.huanzi.ims.util.RsaUtil;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.apache.commons.codec.binary.Base64;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.text.SimpleDateFormat;
    
    /**
     * AES + RSA 加解密AOP处理
     */
    @Aspect
    @Component
    public class SafetyAspect {
    
        /**
         * Pointcut 切入点
         * 匹配cn.huanzi.ims.*.controller包下面的所有方法
         */
        @Pointcut(value = "execution(public * cn.huanzi.ims.*.controller.*.*(..))")
        public void safetyAspect() {
        }
    
        /**
         * 环绕通知
         */
        @Around(value = "safetyAspect()")
        public Object around(ProceedingJoinPoint pjp) {
            try {
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                assert attributes != null;
                //request对象
                HttpServletRequest request = attributes.getRequest();
    
                //http请求方法  post get
                String httpMethod = request.getMethod().toLowerCase();
    
                //method方法
                Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    
                //method方法上面的注解
                Annotation[] annotations = method.getAnnotations();
    
                //方法的形参参数
                Object[] args = pjp.getArgs();
    
                //是否有@Decrypt
                boolean hasDecrypt = false;
                //是否有@Encrypt
                boolean hasEncrypt = false;
                for (Annotation annotation : annotations) {
                    if (annotation.annotationType() == Decrypt.class) {
                        hasDecrypt = true;
                    }
                    if (annotation.annotationType() == Encrypt.class) {
                        hasEncrypt = true;
                    }
                }
    
                //前端公钥
                String publicKey = null;
    
                //jackson
                ObjectMapper mapper = new ObjectMapper();
                //jackson 序列化和反序列化 date处理
                mapper.setDateFormat( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
                //执行方法之前解密,且只拦截post请求
                if ("post".equals(httpMethod) && hasDecrypt) {
                    //AES加密后的数据
                    String data = request.getParameter("data");
                    //后端RSA公钥加密后的AES的key
                    String aesKey = request.getParameter("aesKey");
                    //前端公钥
                    publicKey = request.getParameter("publicKey");
    
                    System.out.println("前端公钥:" + publicKey);
    
                    //后端私钥解密的到AES的key
                    byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey());
                    aesKey = new String(plaintext);
                    System.out.println("解密出来的AES的key:" + aesKey);
    
                    //RSA解密出来字符串多一对双引号
                    aesKey = aesKey.substring(1, aesKey.length() - 1);
    
                    //AES解密得到明文data数据
                    String decrypt = AesUtil.decrypt(data, aesKey);
                    System.out.println("解密出来的data数据:" + decrypt);
    
                    //设置到方法的形参中,目前只能设置只有一个参数的情况
                    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    
                    if(args.length > 0){
                        args[0] = mapper.readValue(decrypt, args[0].getClass());
                    }
                }
    
                //执行并替换最新形参参数   PS:这里有一个需要注意的地方,method方法必须是要public修饰的才能设置值,private的设置不了
                Object o = pjp.proceed(args);
    
                //返回结果之前加密
                if (hasEncrypt) {
                    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                    //每次响应之前随机获取AES的key,加密data数据
                    String key = AesUtil.getKey();
                    System.out.println("AES的key:" + key);
                    String dataString = mapper.writeValueAsString(o);
                    System.out.println("需要加密的data数据:" + dataString);
                    String data = AesUtil.encrypt(dataString, key);
    
                    //用前端的公钥来解密AES的key,并转成Base64
                    String aesKey = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));
    
                    //转json字符串并转成Object对象,设置到Result中并赋值给返回值o
                    o = Result.of(mapper.readValue("{"data":"" + data + "","aesKey":"" + aesKey + ""}", Object.class));
                }
    
                //返回
                return o;
    
            } catch (Throwable e) {
                System.err.println(pjp.getSignature());
                e.printStackTrace();
                return Result.of(null, false, "加解密异常:" + e.getMessage());
            }
        }
    }
    
    SafetyAspect.java
    View Code

      在需要进行加密解密的controller方法上加自定义注解,需要加密@Encrypt,需要解密@Decrypt,两个都有就两个都加

    复制代码
        /**
         * 登录
         */
        @PostMapping("login")
        @Decrypt
        @Encrypt
        public Result<ImsUserVo> login(ImsUserVo userVo, HttpServletResponse response) 
    复制代码

      到这里就准备好了,可以发现除了要在需要进行加密解密的controller方法上加自定义注解,根本不需要对之前的代码进行修改,也不影响之前的业务,当然,因为我们直接重写了$.ajax方法,所以我们需要对所有的post请求的controller都加注解,好在我们用的是单表继承通用common、自动生成单表基础增、删、改、查接口(详情请戳:SpringBoot系列——Spring-Data-JPA(究极进化版) 自动生成单表基础增、删、改、查接口),基础的API我们只需要在common的controller加注解就好了,自定义controller跟重写的controller也要记得加,接下来就可以把项目跑起来进行测试

      效果演示

      未加密之前的效果

      数据直接暴露在http数据包中

      AES与RSA混合加密之后的效果

      http数据包中传输的是密文

     

      解密之后才能看到明文数据

      到这里我们实现了加解密与项目的结合,如果项目已经是按照我前面说的约定的话,即插即用,不影响项目原有业务,直接可以使用这一套,把我的代码拿过去就可以跑起来

      后记

       前后端API交互数据加密——AES与RSA混合加密完整实例先记录到这里,后续有空再更新升级,虽然说没有绝对的安全,但加密总比不加密的要好,整篇文章从头看起来也没什么,比较简单,实际上...中间踩了很多坑,心酸血泪史就不在这里阐述了,希望这篇博客能帮到你

      2019-09-24补充:完成这个混合加密后不久,我试着将它应用在websocket中,而后就有了另一篇文章,感兴趣的可以移步前往阅读《WebSocket数据加密——AES与RSA混合加密

      代码开源

      2019-09-24补充:混合加密的代码一直嵌在ims项目里没有整理出来,而且ims还没完工尚未开源出来,好在前段时间我开源的一个简单通用的后台管理系统,Base Admin(开源一套简单通用的后台管理系统),里面整合了这套API混合加密,大家前往这个项目查看这套API混合加密代码

      代码已经开源、托管到我的GitHub、码云:

      GitHub:https://github.com/huanzi-qch/base-admin

      码云:https://gitee.com/huanzi-qch/base-admin

    package cn.huanzi.ims.annotation;
    import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
    @Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface Encrypt {
    }
    Encrypt

  • 相关阅读:
    【.Net Micro Framework PortingKit 07】NVIC中断处理
    【.Net Micro Framework PortingKit 02】STM3210E平台构建
    【.Net Micro Framework PortingKit 06】设置芯片时钟
    【.Net Micro Framework PortingKit 03】调试初步:点亮LED灯
    【.Net Micro Framework PortingKit 01】移植初步:环境搭建
    开源System.Windows.Forms库,让.Net Micro Framework界面开发和上位机一样简单
    RVDS和MDK嵌入式开发工具调试脚本编写
    JQuery移除事件 简单
    Visual C++ 2008入门经典 第十六章 创建文档和改进视图 简单
    Visual C++ 2008入门经典 第十五章 在窗口中绘图 简单
  • 原文地址:https://www.cnblogs.com/dkws/p/12199739.html
Copyright © 2011-2022 走看看