記一次前後端資料加密的學習

光頭才能強發表於2021-08-07

最近專案涉及到一些敏感資訊,業務要求資料在傳輸過程中需要加密。

這裡資料傳輸包含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

相關文章