ppt做网站,东莞市建设工程交易中心网,iis6.0做网站压缩,网站备案的要求是什么样的前段时间看到一篇文章讲如何保证API调用时数据的安全性#xff08;传送门#xff1a;https://blog.csdn.net/ityouknow/article/details/80603617#xff09;#xff0c;文中讲到利用RSA来加密传输AES的秘钥#xff0c;用AES来加密数据#xff0c;并提供如下思路#xf…前段时间看到一篇文章讲如何保证API调用时数据的安全性传送门https://blog.csdn.net/ityouknow/article/details/80603617文中讲到利用RSA来加密传输AES的秘钥用AES来加密数据并提供如下思路 说人话就是前、后端各自生成自己的RSA秘钥对公钥、私钥然后交换公钥后端给前端的是正常的明文公钥前端给后端的是用后端公钥加密后的密文公钥PS其实我觉得直接交换两个明文公钥就行了后端生成AES的明文key用明文key进行AES加密得到密文数据用前端的公钥进行RSA加密得到密文keyAPI交互时并将密文数据与密文key进行传输前端用自己的私钥进行RAS解密得到明文key用明文key进行AES解密得到明文数据前端给后端发送数据时同理这样一来传输的数据都是密文且只有秘钥才能解密 可惜这篇博客只提供了思路但并没有具体的代码我们在网上查找一下资料开始生撸代码实现一个前后端API交互数据加密——AES与RSA混合加密并应用到项目中
后端加、解密 从网上查找工具类再进行改造
先引入Base64工具类 !-- Base64编码需要 --dependencygroupIdorg.apache.directory.studio/groupIdartifactIdorg.apache.commons.codec/artifactIdversion1.8/version/dependencyAES
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));//调用doFinalbyte[] b cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));// 转base64return Base64.encodeBase64String(b);}/*** 解密** param encryptStr 解密的字符串* param decryptKey 解密的key值*/public static String decrypt(String encryptStr, String decryptKey) throws Exception {//base64格式的key字符串转bytebyte[] 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);}}AesUtilRSA
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 MapString, Object genKeyPair new HashMap();static {try {genKeyPair.putAll(genKeyPair());} catch (Exception e) {e.printStackTrace();}}/*** 生成密钥对(公钥和私钥)*/private static MapString, 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();MapString, Object keyMap new HashMapString, 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);//设置加密、填充方式/*如需使用更多加密、填充方式引入dependencygroupIdorg.bouncycastle/groupIdartifactIdbcprov-jdk16/artifactIdversion1.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);//设置加密、填充方式/*如需使用更多加密、填充方式引入dependencygroupIdorg.bouncycastle/groupIdartifactIdbcprov-jdk16/artifactIdversion1.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简单测试 AES对称加密、解密简单测试
1、字符串
public static void main(String[] args) {//16位String key MIGfMA0GCSqGSIb3;//字符串String str huanzi.qchqq.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.qchqq.com:欢子
加密后dXPRtcdHPQSTwxLnmixkaSvNfGHhg5Gz8sGTtiqCpPo
解密后huanzi.qchqq.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(idnull, userName123456, password111111, nickNamenull, gendernull, avatarnull, emailnull, phonenull, signnull, createdTimenull, updataTimenull)
加密后AXv8ewfYgbuZ/dCmGAxngLryIdlp1NKZ8yyf9bmrBggUBo3be4XRwMAE/DPvFS2HpgeYQTrZM1ECjo01uvZ/T6lY7b2C6L8PTotYHQyJM3kOsYNXL/uyvFZ2EICSQWhmM1XXg0juHLCbgQDMNXc56S/7eH2psu1CTMygUBCF0U/gZaSzqylqujTb3sg7q4xMuxCQ6ne6xmL3ebjanOLeMJHypTDy1rlJTw
解密后ImsUserVo(idnull, userName123456, password111111, nickNamenull, gendernull, avatarnull, emailnull, phonenull, signnull, createdTimenull, updataTimenull)RAS非对称加密、解密简单测试
1、字符串的RSA公钥加密、私钥解密
public static void main(String[] args) {//字符串String str huanzi.qchqq.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();}}私钥MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANG08b2L0Hk1QCJXyTUI6A4CqWKENCedZyJCYMteZ/vx93KeYZbPShhI3IWJJtj9UibiAVRjzmikI9lkKdgnCaOgTmEZis2RWgLzhcOpSqdp/J6dYtmCD6UDeO3E6QPyfVv9d3qPrqaYUCxi7CmouzVaa/cJqrfYB7qGYt3u5AgMBAAECgYBbovQX3ebFcG2MFExKLpAovyUHJo/eeb/vHTrY5aBGMWNnGbks6uW4pWn1ypNIi8AcvwobON6bUtxUrQ8e9OpUlDYTAAqDE8JvJoRC3theHpJbkHCdDLeNnz1EizUwxfe3X3IVwEYd29C00WXt0rUW2D/Fsa7ECp08taeVukAQJBAOyfO8opGp8t40bbyMRVsIR2zK19rN6Kd/NGvjjW/7BPgzDJZsybcN6e0AuhFaWTyHNSonpDztEQ0VWhF0mHokECQQDi4W9xCmzQf0l8mgXUP2IDY5YtQN9g9vL51qEwpcxhHxCCcid62R0y6T2GnRTmkEpSwPYZ2EZQrKtpGiEk4wt5AkAhwwqd6sWApuSB7MQ1t2BLVkQYERGEY0AJ7zmkU7EUmQOpv4C/b7aFODsd9yF1pNIWScTuO8eh37G8AhJlo/BAkEAuIHfME3rGlA5whQ8I1T8b4cgjWLRhrit9tIOiLLqDwsH/mX88b3gPy/pWa/pZW4a74zJeeFn3wc1heC1s2xQJAXHVf9fZaFwDlD6nD3x0Sgu8Mdp8tsfdz2wIkvjtANceojkfxwdZd6PKWgmiPTLKNNqbPaLgtU74WVAnlpSgsw
公钥MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRtPG9i9B5NUAiV8k1COgOAqlvihDQnnWciQmDLXmf78fdynmGWz0oYSNyFiSbY/VPom4gFUY85opCPZZCnYJwmjoE5hGYrNkVoC84XDqUqnafyenfmLZgglA3jtxOkD8n1b/Xd6j66mmFAsYuwpqLs1Wmv3Caq32Ae6hmLd7uQIDAQAB
公钥加密前huanzi.qchqq.com:欢子
公钥加密后MQa65DyVZg/L8SBilLX1yUiajtiTBqUFpQ/qlrSRyMGCubylbp9KisowRghPxk9BuI3ea/4QpidIZKJaZAbQQZKyslSTk3nm6H0BF9pMA7BUeC33xHSy3lJrNOr5SVup1Oir3Nu8i2vJYQV1pPkB5zyUVEcNLD3xr/eNQ
私钥解密后huanzi.qchqq.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/TRAkmXBbVia3BCTrSzLPrKFY8JvLyXqbrV0NrxywY4ZlgR5RscWaj3LtUR63sSXb5ddmOg9XctrWBGvsKrNJAgMBAAECgYEAoql9OPPDzNxdbcnGUQDcP5pYGRx9DL75Cq2KccoHNNRVEGuNkp0HZLLv84GIoFikzS2gUUnyeFmkhck4X0hRqYpCo9DwRsBgBpqn4ebjSu4bd3lG5KCAtMaPC5sAbznY1uuuJnUdul3p9PuF7AmFTsoFFB4YvstvkRna5ZPFA0CQQDfpxPYVpZjOsgng7187vEpFa9vlQxmyamvJ2iAeFLRHCqJwlq4VYqJkgr08SE1XCBqSVhXkLyIPAtdeqxU0iFLAkEA2jJfKVSy4I/BHmk/rdpw7InQ3ERBc/a09t2ZiI3bqtnobTIf/sMZEWPeMkY83RrWL9ZQvMNDa843cans3bm1OwJAIdipGi5QaAf3TnOTc5q9iFgtypcl31BZi5ZNLFQJRHgcvhXzlmzs4oUemkbe3XLugoLgoT24y8jESyFc/iw7QJBAJdR26EENlF6IIoAn8Ln/Oxt30UCqQnNDE8v2wyRSdFmUun/XEQ7xFsDDZeRg1pljinndqS3WWOk92SEjy0UCQQCr5UsIMBAjpGCYXeXrRWYoYdfI6R20IuWoOGzolyKK4ixqMLFuimEwrmXhYnJMzvVHfbLsoogBv9NOP9ffH
公钥MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoEirNhuIha4T6NGixC3xs7AgGgeYc7b3JQ/SdoWU9il37OkuScGdEVkUdvK6h5tHtkTVt382Mji70qKvd5/00QJJlwW1YmtwQk60syz6yhWPCby8l6m61dDa8csGPuGZYEeUfrHFmo9y7VEet7El2XXZjoPV3La1gRr7CqzSQIDAQAB
公钥加密前ImsUserVo(idnull, userName123456, password111111, nickNamenull, gendernull, avatarnull, emailnull, phonenull, signnull, createdTimenull, updataTimenull)
公钥加密后Un1m/CbpzVkkxYrwNOWyEXqpsawxcdv4p3G9bSQRiC/THL8YGIvqFCHnxizzYGB9LEvLbQxw72JB0Wlo1/SvX7AJb2h0ddpvVUkPjmtXNo073SV1zMK9NTCJUMMoHu/TIptxRbVxlBoGMHajq8h2y3RUOPtx/9zhBWlQmzZEifv0MjgAhKX5ucExYfXctcAVGHL959TwKqKQmTENw5o0ElksaA0KIF4L7RvpWVSqZT1Y4O2gMP9ALjamCx6ziRcmk4b4Q5Goph0nmw6nA387qVi3Vz6rQHrIpL0HT5OSiz1O72L3N0Him2IZeAgg3EZCi5xTGl54jGEw
私钥解密后ImsUserVo(idnull, userName123456, password111111, nickNamenull, gendernull, avatarnull, emailnull, phonenull, signnull, createdTimenull, updataTimenull)如需使用更多加密、填充方式引入
dependencygroupIdorg.bouncycastle/groupIdartifactIdbcprov-jdk16/artifactIdversion1.46/version
/dependency加解密的时候改成
Cipher cipher Cipher.getInstance(ALGORITHMS ,new BouncyCastleProvider());重要更新 2020-05-21更新 重要Bug修复后端加解密中不能在代码里new BouncyCastleProvider()JceSecurity. getVerificationResult内部会进行判断如果是新值则每次都会put到map中导致内存缓便被耗尽程序假死崩溃参考博客https://www.bbsmax.com/A/lk5aQo7451/ 应该改成我已经在开源项目改了博客上面之前贴出来的代码我就不改了具体代码大家去开源项目查看看吧 前端加、解密
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 {//获取keygenKey : 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.stringifyplaintext 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.parsedecString JSON.parse(decString);}return decString;}
};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.stringifyplaintext 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.parsedecString JSON.parse(decString);}return decString;}
};简单测试 AES对称加密、解密简单测试 1、字符串
//字符串
let text huanzi.qchqq.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);keyq99IsnEuryk1ZvgX
加密前huanzi.qchqq.com:欢子
key加密后aZn58GtEj9Is0hNWbJoqpRD6RkiBVPCHOvva3Xq2PYo
key解密后huanzi.qchqq.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);keye6gzizHIpDfc6hbg
加密前{username: 欢子, password: 123456, remark: abcd!#$:}
key加密后YdNw5AwteEp8WZs5xMv0YiGcXvX81P9MCLOvroHjfLUyQV/GwJ6obRqi4DT2ucJy8DWrKueOzLGLSQXUVhAgIA
key解密后{username: 欢子, password: 123456, remark: abcd!#$:}RAS非对称加密、解密简单测试
1、字符串的RSA公钥加密、私钥解密
//普通字符串
let text huanzi.qchqq.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);秘钥MIICXQIBAAKBgQDtBKNg9NJ0mMWq99geoi32txkbJuvQ4Wr7x8IzGT8xiGjGOAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP6/9iOLMfe4cHtGqkdX7QPx4uabnIXAREnR4nVl5MtxfvEHXGmEPwIDAQABAoGBANH92gJ85jld3YyoqHa6M4bSC5s2cGEqklWbkLEqQSacp7BrAP2yJ85UPkB9oRtYbr0tkciLYnptshq03TR2r7QT5ovb5KJ2MQExTXk8GZTO/5sSqD0zwA9SESlAmWj8yc49p5Tk0h5UYFgsRATTer1n1llziryXa4QMiIfKrmBAkEAza3DryZnMyNqre6KxFYsFVvIbHRU7tJ5LOiZ43Vl0DXq44zmeNQeh6MzfH6sc0Avu1c61/KNDVf23yfVt7QJBAPGIr0GokOZL0sttiEoQSq/dBdYaSCBfThtrA/9ie8RgcFJkYj4h/6RPzdYIRWDco5RzIoPnZmFC4rfPjFVsCQHE2XFMw3c2TRfj86dKLVxKFbL0UxHNQuYIPIDNW8TtjmaQuwf0LH9bnDUNNzTPaaG87vqOLlEAStVTDWPfzpUCQDVJvbjTstxXfKGufR9FnVMMGFXKOK9mQjU/9m4KPom3vQ9xcGdLJWlstfDE7csR4rkuyf6q4U9sjgZeiH8j8CQQCfaxXfxiDJPztUDm1AKI6uDwz4P4eiYRbbbQ5xiQSunHbq0Y7U9UUkWcLw0xDhReHEYkFuOeiBj2ViAPJz1r0
公钥MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDtBKNg9NJ0mMWq99geoi32txkbJuvQ4Wr7x8IzGT8xiGjGOAuSjvi5yA7IEMMAj8Y8vS7IPPo2mAr/PH0DsNiHMATJm8mNIEDzfP4WOFOdidzqP6/9iOLMfe4cHtGqkdX7QPx4uabnIXAREnR4nVl5MtxfvEHXGmEPwIDAQAB
加密前huanzi.qchqq.com:欢子
公钥加密后aUkUMYC7lF8M1xzcx5ZEdc0DQt4FrqvWqEnD30raVj7rwsfEcyXpPmeF1g2LR86FVG3oxgdTptorkwUDSXB3Tv4av7toGg7Zcf9l1vs5WQX7kCDTitwBVwyBNTZq22xed1J/LAkDujDav6tUJHdMmRKYVe2NeTswvWLOqWWW4
解密后huanzi.qchqq.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);秘钥MIICXAIBAAKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUMzLPmF9e1P/ws2yGHvL6mueU9PxtDJn5rSLsQBSxIkN0QB/nq76S4uh2Nrmmrjomejy5LqXnTVbEoIW2RTFBzyMWy4AjQY6P2pAJ8zCagvcdYcweUIqMQIDAQABAoGAbwrLhkIvjk938nNnaRufoqqrW5OMrzgis6bWlghckawr6NPj5ZPs7nKF/Sv79jdA/N55I6V7bHrxI2NS9Ckm2ygv8nNYimSjzspR48SqVRuH/xYmQQ9hi8Iy4dTlCMud34oXsV2sYI5tEn7f3bypOVfJa6kHSqxe1PIQTxirkCQQDjmHOp2JMcltpL639nnNgZ2U06cRhPHXtcGTIgoqu1Sqp0bKH7QuUF9WPNWxHrbYY5s5jnnhTTwQZg74atTAkEAwXelo0JLYHpML7sLs8aUzitRJXjkW3dY4JPf1wLTNLbawvi4KA/6NA3jx7kCD6KzM7vsWWsRgArrUWa1Dbn6wJARY5pAuZyh1E/IumEBWl0zemQZaT8tekhBSONWY4zzhzNrhqtQkdau4bROLQuBHX9af0u8WcuroGJMsXOG3OiwJBALc91OPJ8cziaPC80Z/QRvXV877HXTCsZ4lNrnBJxOYxvOMp8eyhu4aOWGE1d/QbEKolwj86tq3ikXvfNmOT0ZsCQBkfviB7CKdHCrUCnpAK0upa9x8uFraomDNxFP/HwTFPSOPGhA5pgAzJgygSu2hpEwFFIwfC3EpQ2EAhoeIcdw
公钥MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsAE5TN8kD7U4mFyxBzN1w23Rkf4K8MQ3B0bCZE5crjYp81eUtWrfUMzLPmF9e1P/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、DecryptAOP拦截所有带自定义注解的post请求进行加密解密有Encrypt需要对返回值进行加密有Decrypt需要对参数进行解密加密解密过程与前端的操作同理
前端代码 引入js !--CryptoJS jsencrypt --script th:src{/js/cryptojs.js}/scriptscript th:src{/js/jsencrypt.js}/scriptscript th:src{/js/aesUtil.js}/scriptscript th:src{/js/rsaUtil.js}/script下载CryptoJs跟jsencrypt下来发现CryptoJs需要引入很多js因此在网上找了这个整合的js引它就够了
!function(t,n){objecttypeof exports?module.exportsexportsn():functiontypeof definedefine.amd?define([],n):t.CryptoJSn()}(this,function(){var tt||function(t,n){var iObject.create||function(){function t(){}return function(n){var i;return t.prototypen,inew t,t.prototypenull,i}}(),e{},re.lib{},or.Basefunction(){return{extend:function(t){var ni(this);return tn.mixIn(t),n.hasOwnProperty(init)this.init!n.init||(n.initfunction(){n.$super.init.apply(this,arguments)}),n.init.prototypen,n.$superthis,n},create:function(){var tthis.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.toStringt.toString)},clone:function(){return this.init.prototype.extend(this)}}}(),sr.WordArrayo.extend({init:function(t,i){tthis.wordst||[],i!n?this.sigBytesi:this.sigBytes4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var nthis.words,it.words,ethis.sigBytes,rt.sigBytes;if(this.clamp(),e%4)for(var o0;or;o){var si[o2]24-o%4*8255;n[eo2]|s24-(eo)%4*8}else for(var o0;or;o4)n[eo2]i[o2];return this.sigBytesr,this},clamp:function(){var nthis.words,ithis.sigBytes;n[i2]429496729532-i%4*8,n.lengtht.ceil(i/4)},clone:function(){var to.clone.call(this);return t.wordsthis.words.slice(0),t},random:function(n){for(var i,e[],rfunction(n){var nn,i987654321,e4294967295;return function(){i36969*(65535i)(i16)e,n18e3*(65535n)(n16)e;var r(i16)ne;return r/4294967296,r.5,r*(t.random().5?1:-1)}},o0;on;o4){var ar(4294967296*(i||t.random()));i987654071*a(),e.push(4294967296*a()|0)}return new s.init(e,n)}}),ae.enc{},ca.Hex{stringify:function(t){for(var nt.words,it.sigBytes,e[],r0;ri;r){var on[r2]24-r%4*8255;e.push((o4).toString(16)),e.push((15o).toString(16))}return e.join()},parse:function(t){for(var nt.length,i[],e0;en;e2)i[e3]|parseInt(t.substr(e,2),16)24-e%8*4;return new s.init(i,n/2)}},ua.Latin1{stringify:function(t){for(var nt.words,it.sigBytes,e[],r0;ri;r){var on[r2]24-r%4*8255;e.push(String.fromCharCode(o))}return e.join()},parse:function(t){for(var nt.length,i[],e0;en;e)i[e2]|(255t.charCodeAt(e))24-e%4*8;return new s.init(i,n)}},fa.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)))}},hr.BufferedBlockAlgorithmo.extend({reset:function(){this._datanew s.init,this._nDataBytes0},_append:function(t){stringtypeof t(tf.parse(t)),this._data.concat(t),this._nDataBytest.sigBytes},_process:function(n){var ithis._data,ei.words,ri.sigBytes,othis.blockSize,a4*o,cr/a;cn?t.ceil(c):t.max((0|c)-this._minBufferSize,0);var uc*o,ft.min(4*u,r);if(u){for(var h0;hu;ho)this._doProcessBlock(e,h);var pe.splice(0,u);i.sigBytes-f}return new s.init(p,f)},clone:function(){var to.clone.call(this);return t._datathis._data.clone(),t},_minBufferSize:0}),p(r.Hasherh.extend({cfg:o.extend(),init:function(t){this.cfgthis.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){tthis._append(t);var nthis._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});
//# sourceMappingURLcore.min.js.map
!function(e,t,i){objecttypeof exports?module.exportsexportst(require(./core.min),require(./sha1.min),require(./hmac.min)):functiontypeof definedefine.amd?define([./core.min,./sha1.min,./hmac.min],t):t(e.CryptoJS)}(this,function(e){return function(){var te,it.lib,ri.Base,ni.WordArray,ot.algo,ao.MD5,co.EvpKDFr.extend({cfg:r.extend({keySize:4,hasher:a,iterations:1}),init:function(e){this.cfgthis.cfg.extend(e)},compute:function(e,t){for(var ithis.cfg,ri.hasher.create(),on.create(),ao.words,ci.keySize,fi.iterations;a.lengthc;){sr.update(s);var sr.update(e).finalize(t);r.reset();for(var u1;uf;u)sr.finalize(s),r.reset();o.concat(s)}return o.sigBytes4*c,o}});t.EvpKDFfunction(e,t,i){return c.create(i).compute(e,t)}}(),e.EvpKDF});
//# sourceMappingURLevpkdf.min.js.map
!function(r,e){objecttypeof exports?module.exportsexportse(require(./core.min)):functiontypeof definedefine.amd?define([./core.min],e):e(r.CryptoJS)}(this,function(r){return function(){function e(r,e,t){for(var n[],i0,o0;oe;o)if(o%4){var ft[r.charCodeAt(o-1)]o%4*2,ct[r.charCodeAt(o)]6-o%4*2;n[i2]|(f|c)24-i%4*8,i}return a.create(n,i)}var tr,nt.lib,an.WordArray,it.enc;i.Base64{stringify:function(r){var er.words,tr.sigBytes,nthis._map;r.clamp();for(var a[],i0;it;i3)for(var oe[i2]24-i%4*8255,fe[i12]24-(i1)%4*8255,ce[i22]24-(i2)%4*8255,so16|f8|c,h0;h4i.75*ht;h)a.push(n.charAt(s6*(3-h)63));var pn.charAt(64);if(p)for(;a.length%4;)a.push(p);return a.join()},parse:function(r){var tr.length,nthis._map,athis._reverseMap;if(!a){athis._reverseMap[];for(var i0;in.length;i)a[n.charCodeAt(i)]i}var on.charAt(64);if(o){var fr.indexOf(o);f!-1(tf)}return e(r,t,a)},_map:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/}}(),r.enc.Base64});
//# sourceMappingURLenc-base64.min.js.map
!function(e,t,r){objecttypeof exports?module.exportsexportst(require(./core.min),require(./evpkdf.min)):functiontypeof definedefine.amd?define([./core.min,./evpkdf.min],t):t(e.CryptoJS)}(this,function(e){e.lib.Cipher||function(t){var re,ir.lib,ni.Base,ci.WordArray,oi.BufferedBlockAlgorithm,sr.enc,a(s.Utf8,s.Base64),fr.algo,pf.EvpKDF,di.Ciphero.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.cfgthis.cfg.extend(r),this._xformModee,this._keyt,this.reset()},reset:function(){o.reset.call(this),this._doReset()},process:function(e){return this._append(e),this._process()},finalize:function(e){ethis._append(e);var tthis._doFinalize();return t},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){function e(e){returnstringtypeof 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.StreamCipherd.extend({_doFinalize:function(){var ethis._process(!0);return e},blockSize:1}),r.mode{}),ui.BlockCipherModen.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._ciphere,this._ivt}}),lh.CBCfunction(){function e(e,r,i){var nthis._iv;if(n){var cn;this._ivt}else var cthis._prevBlock;for(var o0;oi;o)e[ro]^c[o]}var ru.extend();return r.Encryptorr.extend({processBlock:function(t,r){var ithis._cipher,ni.blockSize;e.call(this,t,r,n),i.encryptBlock(t,r),this._prevBlockt.slice(r,rn)}}),r.Decryptorr.extend({processBlock:function(t,r){var ithis._cipher,ni.blockSize,ct.slice(r,rn);i.decryptBlock(t,r),e.call(this,t,r,n),this._prevBlockc}}),r}(),_r.pad{},v_.Pkcs7{pad:function(e,t){for(var r4*t,ir-e.sigBytes%r,ni24|i16|i8|i,o[],s0;si;s4)o.push(n);var ac.create(o,i);e.concat(a)},unpad:function(e){var t255e.words[e.sigBytes-12];e.sigBytes-t}},y(i.BlockCipherd.extend({cfg:d.cfg.extend({mode:l,padding:v}),reset:function(){d.reset.call(this);var ethis.cfg,te.iv,re.mode;if(this._xformModethis._ENC_XFORM_MODE)var ir.createEncryptor;else{var ir.createDecryptor;this._minBufferSize1}this._modethis._mode.__creatori?this._mode.init(this,tt.words):(this._modei.call(r,this,tt.words),this._mode.__creatori)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var ethis.cfg.padding;if(this._xformModethis._ENC_XFORM_MODE){e.pad(this._data,this.blockSize);var tthis._process(!0)}else{var tthis._process(!0);e.unpad(t)}return t},blockSize:4}),i.CipherParamsn.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}})),mr.format{},km.OpenSSL{stringify:function(e){var te.ciphertext,re.salt;if(r)var ic.create([1398893684,1701076831]).concat(r).concat(t);else var it;return i.toString(a)},parse:function(e){var ta.parse(e),rt.words;if(1398893684r[0]1701076831r[1]){var ic.create(r.slice(2,4));r.splice(0,4),t.sigBytes-16}return y.create({ciphertext:t,salt:i})}},xi.SerializableCiphern.extend({cfg:n.extend({format:k}),encrypt:function(e,t,r,i){ithis.cfg.extend(i);var ne.createEncryptor(r,i),cn.finalize(t),on.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){ithis.cfg.extend(i),tthis._parse(t,i.format);var ne.createDecryptor(r,i).finalize(t.ciphertext);return n},_parse:function(e,t){returnstringtypeof e?t.parse(e,this):e}}),gr.kdf{},Sg.OpenSSL{execute:function(e,t,r,i){i||(ic.random(8));var np.create({keySize:tr}).compute(e,i),oc.create(n.words.slice(t),4*r);return n.sigBytes4*t,y.create({key:n,iv:o,salt:i})}},Bi.PasswordBasedCipherx.extend({cfg:x.cfg.extend({kdf:S}),encrypt:function(e,t,r,i){ithis.cfg.extend(i);var ni.kdf.execute(r,e.keySize,e.ivSize);i.ivn.iv;var cx.encrypt.call(this,e,t,n.key,i);return c.mixIn(n),c},decrypt:function(e,t,r,i){ithis.cfg.extend(i),tthis._parse(t,i.format);var ni.kdf.execute(r,e.keySize,e.ivSize,t.salt);i.ivn.iv;var cx.decrypt.call(this,e,t,n.key,i);return c}})}()});
//# sourceMappingURLcipher-core.min.js.map
!function(e,i){objecttypeof exports?module.exportsexportsi(require(./core.min)):functiontypeof definedefine.amd?define([./core.min],i):i(e.CryptoJS)}(this,function(e){!function(){var ie,ti.lib,nt.Base,si.enc,rs.Utf8,oi.algo;o.HMACn.extend({init:function(e,i){ethis._hashernew e.init,stringtypeof i(ir.parse(i));var te.blockSize,n4*t;i.sigBytesn(ie.finalize(i)),i.clamp();for(var sthis._oKeyi.clone(),othis._iKeyi.clone(),as.words,fo.words,c0;ct;c)a[c]^1549556828,f[c]^909522486;s.sigByteso.sigBytesn,this.reset()},reset:function(){var ethis._hasher;e.reset(),e.update(this._iKey)},update:function(e){return this._hasher.update(e),this},finalize:function(e){var ithis._hasher,ti.finalize(e);i.reset();var ni.finalize(this._oKey.clone().concat(t));return n}})}()});
//# sourceMappingURLhmac.min.js.map
!function(e,o,r){objecttypeof exports?module.exportsexportso(require(./core.min),require(./cipher-core.min)):functiontypeof definedefine.amd?define([./core.min,./cipher-core.min],o):o(e.CryptoJS)}(this,function(e){return e.mode.ECBfunction(){var oe.lib.BlockCipherMode.extend();return o.Encryptoro.extend({processBlock:function(e,o){this._cipher.encryptBlock(e,o)}}),o.Decryptoro.extend({processBlock:function(e,o){this._cipher.decryptBlock(e,o)}}),o}(),e.mode.ECB});
//# sourceMappingURLmode-ecb.min.js.map
!function(e,r,i){objecttypeof exports?module.exportsexportsr(require(./core.min),require(./cipher-core.min)):functiontypeof definedefine.amd?define([./core.min,./cipher-core.min],r):r(e.CryptoJS)}(this,function(e){return e.pad.Pkcs7});
//# sourceMappingURLpad-pkcs7.min.js.map
!function(e,r,i){objecttypeof exports?module.exportsexportsr(require(./core.min),require(./enc-base64.min),require(./md5.min),require(./evpkdf.min),require(./cipher-core.min)):functiontypeof definedefine.amd?define([./core.min,./enc-base64.min,./md5.min,./evpkdf.min,./cipher-core.min],r):r(e.CryptoJS)}(this,function(e){return function(){var re,ir.lib,ni.BlockCipher,or.algo,t[],c[],s[],f[],a[],d[],u[],v[],h[],y[];!function(){for(var e[],r0;r256;r)r128?e[r]r1:e[r]r1^283;for(var i0,n0,r0;r256;r){var on^n1^n2^n3^n4;oo8^255o^99,t[i]o,c[o]i;var pe[i],le[p],_e[l],k257*e[o]^16843008*o;s[i]k24|k8,f[i]k16|k16,a[i]k8|k24,d[i]k;var k16843009*_^65537*l^257*p^16843008*i;u[o]k24|k8,v[o]k16|k16,h[o]k8|k24,y[o]k,i?(ip^e[e[e[_^p]]],n^e[e[n]]):in1}}();var p[0,1,2,4,8,16,32,64,128,27,54],lo.AESn.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!this._key){for(var ethis._keyPriorResetthis._key,re.words,ie.sigBytes/4,nthis._nRoundsi6,o4*(n1),cthis._keySchedule[],s0;so;s)if(si)c[s]r[s];else{var fc[s-1];s%i?i6s%i4(ft[f24]24|t[f16255]16|t[f8255]8|t[255f]):(ff8|f24,ft[f24]24|t[f16255]16|t[f8255]8|t[255f],f^p[s/i|0]24),c[s]c[s-i]^f}for(var athis._invKeySchedule[],d0;do;d){var so-d;if(d%4)var fc[s];else var fc[s-4];d4||s4?a[d]f:a[d]u[t[f24]]^v[t[f16255]]^h[t[f8255]]^y[t[255f]]}}},encryptBlock:function(e,r){this._doCryptBlock(e,r,this._keySchedule,s,f,a,d,t)},decryptBlock:function(e,r){var ie[r1];e[r1]e[r3],e[r3]i,this._doCryptBlock(e,r,this._invKeySchedule,u,v,h,y,c);var ie[r1];e[r1]e[r3],e[r3]i},_doCryptBlock:function(e,r,i,n,o,t,c,s){for(var fthis._nRounds,ae[r]^i[0],de[r1]^i[1],ue[r2]^i[2],ve[r3]^i[3],h4,y1;yf;y){var pn[a24]^o[d16255]^t[u8255]^c[255v]^i[h],ln[d24]^o[u16255]^t[v8255]^c[255a]^i[h],_n[u24]^o[v16255]^t[a8255]^c[255d]^i[h],kn[v24]^o[a16255]^t[d8255]^c[255u]^i[h];ap,dl,u_,vk}var p(s[a24]24|s[d16255]16|s[u8255]8|s[255v])^i[h],l(s[d24]24|s[u16255]16|s[v8255]8|s[255a])^i[h],_(s[u24]24|s[v16255]16|s[a8255]8|s[255d])^i[h],k(s[v24]24|s[a16255]16|s[d8255]8|s[255u])^i[h];e[r]p,e[r1]l,e[r2]_,e[r3]k},keySize:8});r.AESn._createHelper(l)}(),e.AES});
//# sourceMappingURLaes.min.js.map
!function(e,n){objecttypeof exports?module.exportsexportsn(require(./core.min)):functiontypeof definedefine.amd?define([./core.min],n):n(e.CryptoJS)}(this,function(e){return e.enc.Utf8});
//# sourceMappingURLenc-utf8.min.js.map获取后端公钥
script th:inlinejavascript//获取后端RSA公钥并存到sessionStoragesessionStorage.setItem(javaPublicKey, [[${publicKey}]]);
/script重写ajax以及生成前端密钥对因为我们项目中大部分都是使用$.ajax方法所有重写它比较合适并且我们只拦截post请求
script//获取前端RSA公钥密码、AES的key并放到windowlet 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的keylet aesKey aesUtil.genKey();data {data: aesUtil.encrypt(data, aesKey),//AES加密后的数据aesKey: rsaUtil.encrypt(aesKey, sessionStorage.getItem(javaPublicKey)),//后端RSA公钥加密后的AES的keypublicKey: 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后端代码
两个自定义注解
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 {}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 {}aop扫描所有的controller拦截带自定义标签的post请求进行解密、加密再将明文设置回去如何使用aop请戳我之前的博客SpringBoot系列——aop 面向切面
要注意的是
1、我们在aop只设置了第一个参数因此controller方法需要是实体接参且第一个参数就是所有要求要么有一个实体Vo参数要么没有参数
2、对于返回值需要是统一的返回值因为我们目前是按统一的返回值设置值的例如本例中的Result是我们约定好的统一返回值后续升级可以用反射来设置值
3、还有一个需要注意的地方method方法必须是要public修饰的才能设置方法的形参值private的设置不了
PS2019-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 getString httpMethod request.getMethod().toLowerCase();//method方法Method method ((MethodSignature) pjp.getSignature()).getMethod();//method方法上面的注解Annotation[] annotations method.getAnnotations();//方法的形参参数Object[] args pjp.getArgs();//是否有Decryptboolean hasDecrypt false;//是否有Encryptboolean hasEncrypt false;for (Annotation annotation : annotations) {if (annotation.annotationType() Decrypt.class) {hasDecrypt true;}if (annotation.annotationType() Encrypt.class) {hasEncrypt true;}}//前端公钥String publicKey null;//jacksonObjectMapper 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的keyString aesKey request.getParameter(aesKey);//前端公钥publicKey request.getParameter(publicKey);System.out.println(前端公钥 publicKey);//后端私钥解密的到AES的keybyte[] 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并转成Base64String aesKey Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));//转json字符串并转成Object对象设置到Result中并赋值给返回值oo 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());}}
}在需要进行加密解密的controller方法上加自定义注解需要加密Encrypt需要解密Decrypt两个都有就两个都加
/*** 登录*/PostMapping(login)DecryptEncryptpublic ResultImsUserVo 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、码云
GitHubhttps://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