记一次前后端数据加密的学习
最近项目涉及到一些敏感信息,业务要求数据在传输过程中需要加密。
这里数据传输包含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()
},
- 执行结果