最近專案涉及到一些敏感資訊,業務要求資料在傳輸過程中需要加密。
這裡資料傳輸包含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程式碼
- 獲取金鑰對
/**
* 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;
}
- 公鑰加密
/**
* 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;
}
- 私鑰解密
/**
* 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;
}
- 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);
}
- 執行結果
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 前端程式碼
- 引入加密庫
npm i jsencrypt
import JSEncrypt from 'jsencrypt'
- 加密
const encrypt = new JSEncrypt();
encrypt.setPublicKey('你的公鑰');
var encryptData = encrypt.encrypt(‘你的資料’);// 加密後的字串
- 解密
const decrypt =new JSEncrypt()
decrypt.setPrivateKey(privateKey)
var decryptData = decrypt.decrypt(msg)
- 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))
- 執行結果
2.2.AES標準加密
2.2.1.Java 程式碼實現
- 隨機生成 加密 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);
}
- 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;
}
}
- 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;
}
}
- 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);
}
- 執行結果
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 前端程式碼
- 引入庫
npm install crypto-js
import CryptoJS from 'crypto-js/crypto-js'
- 解密
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()
},
- 加密
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);
-
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()
},
- 執行結果