• 记一次前后端数据加密的学习


    最近项目涉及到一些敏感信息,业务要求数据在传输过程中需要加密。

    这里数据传输包含2中

    1. 前后端数据传输过程
    2. 与其它服务(系统)数据交互时,数据的传输过程

    这里我们先简要介绍加密算法的优缺点。再通过前后端(vue、java)代码的形式,演示加密解密的demo

    1.加密算法简介

    参考并整理了目前比较流行的2种加密算法

    1.1.RAS 非对称加密

    RAS 加密是基于质数对的方式加密的,具体原理以及优缺点可以参考RSA加密算法原理简书

    该加密方式存在缺点,明文的长度不能超过 117 bytes,如果超长,就会产生异常

    javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
    	at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:346)
    	at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:391)
    	at javax.crypto.Cipher.doFinal(Cipher.java:2168)
    	at com.xsaas.hr.stockapp.base.utils.RsaUtils.encrypt(RsaUtils.java:137)
    	at com.xsaas.RasTest.testRas(RasTest.java:122)
    

    可以通过分段加密的方式进行处理此种问题。但是 JS 前端代码中分段加密存在缺陷,偶尔会有数据无法解密的情况(这里我没有深究,不清楚原因),分段加解密也存在代码繁琐等问题(常规加密方式是直接调用库,这里在调用库的基础上,还需要加工代码,可能这也是导致解密失败的原因)

    1.2.AES 标准加密

    AES 加密的原理详解见AES 加密算法的原理详解,微信小程序加密传输就是用这个加密算法加密的。

    该算法比较简答,属于对称加密,但是没有 RAS 非对称加密 安全。

    2. 代码实现

    2.1.RAS 非对称加密

    2.1.1.Java代码

    1. 获取密钥对
        /**
         * RAS非对称加密,随机生成密钥对
         *
         * @return 密钥对
         */
        public static Map<String, String> genKeyPair() {
            // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
            KeyPairGenerator keyPairGen = null;
            try {
                keyPairGen = KeyPairGenerator.getInstance("RSA");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            // 初始化密钥对生成器,密钥大小为96-1024位
            assert keyPairGen != null;
            keyPairGen.initialize(1024, new SecureRandom());
            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
            // 将公钥和私钥保存到Map
            Map<String, String> res = new HashMap<String, String>(2) {{
                put(AdminConstant.PUBLIC_KEY, new String(Base64.encodeBase64(publicKey.getEncoded())));
                put(AdminConstant.PRIVATE_KEY, new String(Base64.encodeBase64((privateKey.getEncoded()))));
            }};
            return res;
        }
    
    1. 公钥加密
        /**
         * RAS非对称加密: 公钥加密
         *
         * @param str       加密字符串
         * @param publicKey 公钥
         * @return 密文
         */
        public static String encrypt(String str, String publicKey) {
            //base64编码的公钥
            byte[] decoded = Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey;
            String outStr = null;
    
            try {
                pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
                Cipher cipher = Cipher.getInstance("RSA");
                cipher.init(Cipher.ENCRYPT_MODE, pubKey);
                outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
            } catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            //RSA加密
            return outStr;
        }
    
    1. 私钥解密
        /**
         * RSA私钥解密
         *
         * @param str        加密字符串
         * @param privateKey 私钥
         * @return 铭文
         */
        public static String decrypt(String str, String privateKey) {
            //64位解码加密后的字符串
            byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
            //base64编码的私钥
            byte[] decoded = Base64.decodeBase64(privateKey);
            RSAPrivateKey priKey;
            //RSA解密
            Cipher cipher;
            String outStr = null;
    
            try {
                priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
                cipher = Cipher.getInstance("RSA");
                cipher.init(Cipher.DECRYPT_MODE, priKey);
                outStr = new String(cipher.doFinal(inputByte));
            } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
                e.printStackTrace();
            }
            return outStr;
        }
    
    1. demo
        @Test
        public void testRasDemo() {
            JSONObject object = new JSONObject();
            object.put("name", "隔壁老樊");
            object.put("id", "123456");
            object.put("age", 20);
            // 随机获取秘钥对
            Map<String, String> keyPair = RsaUtils.genKeyPair();
            // 秘钥
            String privateKey = keyPair.get(AdminConstant.PUBLIC_KEY);
            // 公钥
            String publicKey = keyPair.get(AdminConstant.PUBLIC_KEY);
            // 加密后的数据
            String encryptData = null;
            try {
                // 使用公钥加密
                encryptData = RsaUtils.encrypt(JSON.toJSONString(object), publicKey);
            } catch (Exception e) {
                log.error("加密失败", e);
            }
            // 解密后的数据
            String decryptData = null;
            try {
                // 使用私钥解密
                decryptData = RsaUtils.decrypt(encryptData, privateKey);
            } catch (Exception e) {
                e.printStackTrace();
            }
            log.info("原始明文数据={}", object.toJSONString());
            log.info("公钥={}", publicKey);
            log.info("私钥={}", privateKey);
            log.info("加密后的数据={}", encryptData);
            log.info("解密后的数据={}", decryptData);
        }
    
    1. 执行结果
    10:14:26.814 [main] INFO com.xsaas.RasTest - 原始明文数据={"name":"隔壁老樊","id":"123456","age":20}
    10:14:26.822 [main] INFO com.xsaas.RasTest - 公钥=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEczsBcEfFdljhQ/MsMythd96xpPR1RzrveDwAwazETBPnmxuK8MEKecY/B3cdGmSEcPlZRy3hDgxwVfSgxcxqIXt68S3/U+jfndrCtPbJJ+SYj61X/MT82lqFWuhF0lNj2RbyjohzaW+GSBqLbX7gyuZRw57ZcoXpRhx+bNvhLQIDAQAB
    10:14:26.822 [main] INFO com.xsaas.RasTest - 私钥=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIRzOwFwR8V2WOFD8ywzK2F33rGk9HVHOu94PADBrMRME+ebG4rwwQp5xj8Hdx0aZIRw+VlHLeEODHBV9KDFzGohe3rxLf9T6N+d2sK09skn5JiPrVf8xPzaWoVa6EXSU2PZFvKOiHNpb4ZIGottfuDK5lHDntlyhelGHH5s2+EtAgMBAAECgYAG1DYfnd1ldfOhMbKw/bZn4RlPSXT9Mv376NQXKeUxfcas81dZM46QbrTk/QqMKpcyKO0CSGQ6LVJA3H2vaGNgpmvj36qKiZ3AKsbc90JvRWlULBk1XlmyduU6i+hS8dPZXuh3GKum8kpCjK/Qb1e8hKu+1EQ3wQFO0EYSjbq7wQJBAMbgXMmuQ0cVR9qAnG8mq482ES6q16xTUPyMutO9NdOraoZQP15HFKAEFs8m05Hy0Hsw4IobxAg+A1dYgE1t/j0CQQCqfnUWRQDn6JhnrYHcGDNtc7F4ZDLrOI+P8ZfVNoOFOaY7EzgkAD5w1pM8d2IoqPCJRJg9wExAAD2yZFF53g2xAkEAtJyB5+9Izj93V+rBJviZiZ/yjs08vRWVUSaFbVJClg7w2TX7tqUbCA9un4aFUeCQkbBb21FIAKxA4IxRSQCBiQJADBF9ekESKlhFiXk3qvuvkDzTQCFflVTgnKDOTZJZRvHouV/H5ox53wThUTNmKFilBiJr4FsfSpx5wYnmVokIUQJAexXN46GNJZYZzz2Twtj/lJ/dOEToplh6AG0uLTtjkJ2H2hiha8oFgwICQKj8LYcLSpqwUsxVQa4IkhYzMmPzJQ==
    10:14:26.822 [main] INFO com.xsaas.RasTest - 加密后的数据=LpD/ZLlQugYYpuaPYfkpX6ZNjRZlS+5516jos92O8rSnPnlBMzqtqohWrPugtZ25jvDjSzNrxKvLiSh9EweCl93QjomjUsuhia5TjpSV5smDqjUezxLQGP/E+zamZw9ZvaRdJstd4kyBfhQwYb66m9HiwK8qa4EpzuUyr94hcHw=
    10:14:26.822 [main] INFO com.xsaas.RasTest - 解密后的数据={"name":"隔壁老樊","id":"123456","age":20}
    

    2.1.2 VUE 前端代码

    1. 引入加密库
    npm i jsencrypt
    import JSEncrypt from 'jsencrypt'
    
    1. 加密
    const encrypt = new JSEncrypt();
    encrypt.setPublicKey('你的公钥');
    var encryptData = encrypt.encrypt(‘你的数据’);// 加密后的字符串
    
    1. 解密
    const decrypt =new JSEncrypt()
    decrypt.setPrivateKey(privateKey)
    var decryptData = decrypt.decrypt(msg)
    
    1. Demo

    对 java 加密后的数据进行解密,解密结果如下

          const privateKey = 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIRzOwFwR8V2WOFD8ywzK2F33rGk9HVHOu94PADBrMRME+ebG4rwwQp5xj8Hdx0aZIRw+VlHLeEODHBV9KDFzGohe3rxLf9T6N+d2sK09skn5JiPrVf8xPzaWoVa6EXSU2PZFvKOiHNpb4ZIGottfuDK5lHDntlyhelGHH5s2+EtAgMBAAECgYAG1DYfnd1ldfOhMbKw/bZn4RlPSXT9Mv376NQXKeUxfcas81dZM46QbrTk/QqMKpcyKO0CSGQ6LVJA3H2vaGNgpmvj36qKiZ3AKsbc90JvRWlULBk1XlmyduU6i+hS8dPZXuh3GKum8kpCjK/Qb1e8hKu+1EQ3wQFO0EYSjbq7wQJBAMbgXMmuQ0cVR9qAnG8mq482ES6q16xTUPyMutO9NdOraoZQP15HFKAEFs8m05Hy0Hsw4IobxAg+A1dYgE1t/j0CQQCqfnUWRQDn6JhnrYHcGDNtc7F4ZDLrOI+P8ZfVNoOFOaY7EzgkAD5w1pM8d2IoqPCJRJg9wExAAD2yZFF53g2xAkEAtJyB5+9Izj93V+rBJviZiZ/yjs08vRWVUSaFbVJClg7w2TX7tqUbCA9un4aFUeCQkbBb21FIAKxA4IxRSQCBiQJADBF9ekESKlhFiXk3qvuvkDzTQCFflVTgnKDOTZJZRvHouV/H5ox53wThUTNmKFilBiJr4FsfSpx5wYnmVokIUQJAexXN46GNJZYZzz2Twtj/lJ/dOEToplh6AG0uLTtjkJ2H2hiha8oFgwICQKj8LYcLSpqwUsxVQa4IkhYzMmPzJQ=='
          const msg = 'LpD/ZLlQugYYpuaPYfkpX6ZNjRZlS+5516jos92O8rSnPnlBMzqtqohWrPugtZ25jvDjSzNrxKvLiSh9EweCl93QjomjUsuhia5TjpSV5smDqjUezxLQGP/E+zamZw9ZvaRdJstd4kyBfhQwYb66m9HiwK8qa4EpzuUyr94hcHw='
          const encrypt = new JSEncrypt()
          encrypt.setPrivateKey(privateKey)
          console.log('加密前的数据=' + msg)
          console.log('解密后的数据=' + encrypt.decrypt(msg))
    
    1. 执行结果

    image

    2.2.AES标准加密

    2.2.1.Java 代码实现

    1. 随机生成 加密 key

    此处使用默认标准,加密key的长度为16位,此代码为随机生成16为编码

        /**
         * 随机生成16位的编码
         */
        public static String getPrivateKey() {
            //随机生成一位整数
            int random = (int) (Math.random() * 9 + 1);
            String valueOf = String.valueOf(random);
            //生成uuid的hashCode值
            int hashCode = UUID.randomUUID().toString().hashCode();
            //可能为负数
            if (hashCode < 0) {
                hashCode = -hashCode;
            }
            return valueOf + String.format("%015d", hashCode);
        }
    
    1. AES解密
        /**
         * AES算法:解密方法
         *
         * @param data 要解密的数据
         * @param key  解密key
         * @param iv   解密iv
         * @return 解密的结果
         * @throws Exception
         */
        public static String desEncrypt(String data, String key, String iv) throws Exception {
            try {
                byte[] encrypted1 = new Base64().decode(data);
    
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
                SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
    
                cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
    
                byte[] original = cipher.doFinal(encrypted1);
                String originalString = new String(original);
                return originalString;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    1. AES 加密
        /**
         * AES算法:AES加密方法
         *
         * @param data 要加密的数据
         * @param key  加密key
         * @param iv   加密iv
         * @return 加密的结果
         * @throws Exception
         */
        public static String encrypt(String data, String key, String iv) throws Exception {
            try {
    
                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");//"算法/模式/补码方式"NoPadding PkcsPadding
                int blockSize = cipher.getBlockSize();
    
                byte[] dataBytes = data.getBytes();
                int plaintextLength = dataBytes.length;
                if (plaintextLength % blockSize != 0) {
                    plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
                }
    
                byte[] plaintext = new byte[plaintextLength];
                System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
    
                SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
                IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
    
                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
                byte[] encrypted = cipher.doFinal(plaintext);
                return new Base64().encodeToString(encrypted);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    1. Demo
       @Test
        public void testAesDemo() {
            // 原始数据
            JSONObject object = new JSONObject();
            object.put("name", "隔壁老樊");
            object.put("id", "123456");
            object.put("age", 20);
            // 随机生成 16 位 key
            String privateKey = RsaUtils.getPrivateKey();
            String ivKey = RsaUtils.getPrivateKey();
            // 加密后的数据
            String encryptData = null;
            try {
                encryptData = RsaUtils.encrypt(JSON.toJSONString(object), privateKey, ivKey);
            } catch (Exception e) {
                log.error("加密失败", e);
            }
            // 解密后的数据
            String decryptData = null;
            try {
                decryptData = RsaUtils.desEncrypt(encryptData, privateKey, ivKey);
            } catch (Exception e) {
                log.error("解密失败", e);
            }
            log.info("原始数据={}", object.toJSONString());
            log.info("privateKey={}", "privateKey");
            log.info("ivKey={}", ivKey);
            log.info("加密后的数据={}", encryptData);
            log.info("解密后的数据={}", decryptData);
        }
    
    1. 执行结果
    10:43:00.142 [main] INFO com.xsaas.RasTest - 原始数据={"name":"隔壁老樊","id":"123456","age":20}
    10:43:00.148 [main] INFO com.xsaas.RasTest - privateKey=8000000915632506
    10:43:00.148 [main] INFO com.xsaas.RasTest - ivKey=7000001933997723
    10:43:00.148 [main] INFO com.xsaas.RasTest - 加密后的数据=3oMUkGtm7Yp1V2zCQJqLt9u5BrbOPDqL56zX5ycRx+znREu9UEsJHxlPNgBvaoiW
    10:43:00.148 [main] INFO com.xsaas.RasTest - 解密后的数据={"name":"隔壁老樊","id":"123456","age":20}  
    

    2.2.2.VUE 前端代码

    1. 引入库
    npm install crypto-js
    import CryptoJS from 'crypto-js/crypto-js'
    
    1. 解密
    decrypt(word, keyStr, ivStr) {
      let key = CryptoJS.enc.Utf8.parse(keyStr)
      let iv = CryptoJS.enc.Utf8.parse(ivStr)
      if (keyStr) {
        key = CryptoJS.enc.Utf8.parse(keyStr)
        iv = CryptoJS.enc.Utf8.parse(ivStr)
      }
      const base64 = CryptoJS.enc.Base64.parse(word)
      const src = CryptoJS.enc.Base64.stringify(base64)
      var decrypt = CryptoJS.AES.decrypt(src, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.ZeroPadding
      })
      var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
      return decryptedStr.toString()
    },
    
    1. 加密
    export function Encrypt(word, keyStr, ivStr) {
    let key = KEY
    let iv = IV
    if (keyStr) {
       key = CryptoJS.enc.Utf8.parse(keyStr);
       iv = CryptoJS.enc.Utf8.parse(ivStr);
    }
    let srcs = CryptoJS.enc.Utf8.parse(word);
    var encrypted = CryptoJS.AES.encrypt(srcs, key, {
       iv: iv,
       mode: CryptoJS.mode.CBC,
       padding: CryptoJS.pad.ZeroPadding
    });
    console.log("-=-=-=-", encrypted.ciphertext)
    return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
    
    1. Demo

      基于上述加密加密数据进行解密

          const word = '3oMUkGtm7Yp1V2zCQJqLt9u5BrbOPDqL56zX5ycRx+znREu9UEsJHxlPNgBvaoiW'
          const keyStr = '8000000915632506'
          const ivStr = '7000001933997723'
          console.log('=============' + this.decrypt(word, keyStr, ivStr))
    
        decrypt(word, keyStr, ivStr) {
          let key = CryptoJS.enc.Utf8.parse(keyStr)
          let iv = CryptoJS.enc.Utf8.parse(ivStr)
    
          if (keyStr) {
            key = CryptoJS.enc.Utf8.parse(keyStr)
            iv = CryptoJS.enc.Utf8.parse(ivStr)
          }
          const base64 = CryptoJS.enc.Base64.parse(word)
          const src = CryptoJS.enc.Base64.stringify(base64)
          var decrypt = CryptoJS.AES.decrypt(src, key, {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.ZeroPadding
          })
    
          var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
          return decryptedStr.toString()
        },
    
    1. 执行结果
      image
  • 相关阅读:
    柱状图 highcharts 柱状图默认是显示的 Heighcharts.com 的版权。设置去掉不显示(非商业)
    eclipse下使用maven配置库托管jar包
    Java的云打印Lodop
    文本框限制输入类型<input>的输入框
    初次使用JFinal
    【原创】java实现两单链表相加求和
    【原创】Springboot的Filter拦截器中使用@value获取值为null
    【原创】Oracle主从同步---创建物理备份数据库[Creating a Physical Standby Database]
    【原创】基于Telnet协议的Jenkins远程部署
    【原创】FastDFS简单安装配置-----同一台机器测试
  • 原文地址:https://www.cnblogs.com/dz-boss/p/15111295.html
Copyright © 2020-2023  润新知