对app请求的参数和响应进行rsa加密和解密
功能實(shí)現(xiàn)流程圖:
參考:
鏈接: https://www.cnblogs.com/throwable/p/9471938.html.
鏈接: https://www.cnblogs.com/whcghost/p/5657594.html.
鏈接: https://blog.csdn.net/charry0110/article/details/109495035.
鏈接: https://www.cnblogs.com/elaron/archive/2013/04/09/3010375.html.
鏈接: https://blog.csdn.net/qq_35164962/article/details/102704880.
功能說明:
主要是為了實(shí)現(xiàn)對app的請求的參數(shù)和響應(yīng)進(jìn)行加密,本次只做了json格式參數(shù)的post請求的加解密。主要考慮到get參數(shù)加密后太長會(huì)超過url長度限制。上傳下載的請求也沒有加解密,主要擔(dān)心加解密時(shí)間太長。
除了對參數(shù)和響應(yīng)內(nèi)容進(jìn)行加密外,還進(jìn)行了時(shí)間戳的過期檢查操作和簽名驗(yàn)證的操作,來防止偽造請求訪問。但是只對時(shí)間戳拼接一個(gè)頭的字符串進(jìn)行簽名,并沒有使用參數(shù)內(nèi)容進(jìn)行簽名,想想只要破解不了我的加密方式,我用時(shí)間戳來簽名應(yīng)該就夠了。
主要實(shí)現(xiàn)代碼
json請求接收器
package com.wx.security;import com.fasterxml.jackson.databind.ObjectMapper; import com.wx.exception.BadRequestException; import org.springframework.core.MethodParameter; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonInputMessage; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.Type; import java.net.URLDecoder; import java.nio.charset.Charset; import java.util.Date;/****/ //@ControllerAdvice(basePackages = "com.wx.*.controller") @ControllerAdvice public class CustomRequestBodyAdvice extends RequestBodyAdviceAdapter {private final ObjectMapper objectMapper;public CustomRequestBodyAdvice(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType) {return RsaProperties.enSwitch;}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType) throws IOException {String content = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("UTF-8"));EncryptModel in = objectMapper.readValue(content, EncryptModel.class);//先校驗(yàn)一下時(shí)間戳是否過期,如果時(shí)間戳和后臺當(dāng)前時(shí)間相差超過5分鐘,則請求判定為失效if (Math.abs(in.getTimestamp() - new Date().getTime()) > RsaProperties.deadlineMins * 1000 * 60) {throw new BadRequestException("請求已過期!");}String inRawSign = in.getTimestamp() + "";String inSign = null;try {inSign = ShaUtils.SINGLETON.sha(inRawSign);} catch (Exception e) {throw new IllegalArgumentException("驗(yàn)證參數(shù)簽名失敗!");}if (!inSign.equals(in.getSign())) {throw new IllegalArgumentException("驗(yàn)證參數(shù)簽名失敗!");}String decryptedData = null;try {decryptedData = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, in.getData());} catch (Exception e) {throw new IllegalArgumentException("參數(shù)解密失敗:" + in.getData(), e);}ByteArrayInputStream inputStream = new ByteArrayInputStream(URLDecoder.decode(decryptedData, "UTF-8").getBytes(Charset.forName("UTF-8")));return new MappingJacksonInputMessage(inputStream, inputMessage.getHeaders());} }響應(yīng)解析器
package com.wx.security;import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.core.MethodParameter; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonValue; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;import java.net.URLEncoder;/****/ @ControllerAdvice public class CustomResponseBodyAdvice extends JsonViewResponseBodyAdvice {private final ObjectMapper objectMapper;public CustomResponseBodyAdvice(ObjectMapper objectMapper) {this.objectMapper = objectMapper;}@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return RsaProperties.enSwitch;}@Overrideprotected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,MethodParameter returnType, ServerHttpRequest request,ServerHttpResponse response) {if (request.getMethod() == HttpMethod.POST) {EncryptModel out = new EncryptModel();out.setTimestamp(System.currentTimeMillis());String decryptedData = null;try {final String jsonStr = objectMapper.writeValueAsString(bodyContainer.getValue());decryptedData = RsaUtils.encryptByPublicKey(RsaProperties.publicKey, URLEncoder.encode(jsonStr, "UTF-8"));out.setData(decryptedData);} catch (Exception e) {throw new IllegalArgumentException("返回對象加密失敗:" + bodyContainer.toString(), e);}String rawSign = out.getTimestamp() + "";try {out.setSign(ShaUtils.SINGLETON.sha(rawSign));} catch (Exception e) {throw new IllegalArgumentException("返回對象簽名失敗:" + rawSign);}bodyContainer.setValue(out);}} }加密后接收對象
package com.wx.security;/*** @Author: 遛貓達(dá)人* @Description: TODO* @DateTime: 2021/8/9 11:44**/ public class EncryptModel {private String data;private Long timestamp;private String sign;public String getData() {return data;}public void setData(String data) {this.data = data;}public Long getTimestamp() {return timestamp;}public void setTimestamp(Long timestamp) {this.timestamp = timestamp;}public String getSign() {return sign;}public void setSign(String sign) {this.sign = sign;} }簽名工具:
package com.wx.security;import org.apache.commons.codec.binary.Hex;import java.security.MessageDigest;public enum ShaUtils {/*** SINGLETON*/SINGLETON;private static final String SECRET = "xxx";private static final String CHARSET = "UTF-8";/*** @param raw* @return* @throws Exception*/public String sha(String raw) throws Exception {MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");messageDigest.update((SECRET + raw).getBytes(CHARSET));return Hex.encodeHexString(messageDigest.digest());}}前端加密
post(api, data, options) {var newData = {};if(store.state.enSwitch){let jsonData = JSON.stringify(data);// console.log(jsonData)newData.timestamp = new Date().getTime();let shaContent = sha_prefix + newData.timestamp;newData.sign =sha256(shaContent);newData.data = encrypt(encodeURIComponent(jsonData));}else{newData = data}return this.getRequest(api, newData, options, 'POST') },前端解密
//解密post請求響應(yīng)if(store.state.enSwitch && method === 'POST'){let sign =sha256(sha_prefix + res.data.timestamp);if(sign !== res.data.sign){reject(res)uni.showToast({icon: 'none',title: "簽名校驗(yàn)不通過"})return false}let resData = decrypt(res.data.data);// console.log(api+":"+decodeURIComponent(resData))res.data = JSON.parse(decodeURIComponent(resData))}注意:使用前端加解密工具前先要執(zhí)行以下命令安裝相關(guān)依賴包:
npm install jsencrypt
npm install js-sha256
npm i encryptlong -S
前端加解密工具:
后端加解密工具:
package com.wx.security;import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;/*** @author* @description Rsa 工具類,公鑰私鑰生成,加解密* @date**/ public class RsaUtils {private static final String SRC = "1123456";public static final String RSA = "RSA";/** *//*** RSA最大加密明文大小*/private static final int MAX_ENCRYPT_BLOCK = 117;/** *//*** RSA最大解密密文大小*/private static final int MAX_DECRYPT_BLOCK = 128;public static final String CHARSET_NAME = "UTF-8";/*** @param args* @throws Exception*/public static void main(String[] args) throws Exception {System.out.println("\n");RsaKeyPair keyPair = generateKeyPair();System.out.println("公鑰:" + keyPair.getPublicKey());System.out.println("私鑰:" + keyPair.getPrivateKey());System.out.println("\n");test1(keyPair);System.out.println("\n");}/*** 公鑰加密私鑰解密*/private static void test1(RsaKeyPair keyPair) throws Exception {System.out.println("***************** 公鑰加密私鑰解密開始 *****************");String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);System.out.println("加密前:" + RsaUtils.SRC);System.out.println("加密后:" + text1);System.out.println("解密后:" + text2);if (RsaUtils.SRC.equals(text2)) {System.out.println("解密字符串和原始字符串一致,解密成功");} else {System.out.println("解密字符串和原始字符串不一致,解密失敗");}System.out.println("***************** 公鑰加密私鑰解密結(jié)束 *****************");}/*** 私鑰解密** @param privateKeyText 私鑰* @param text 待解密的文本* @return /* @throws Exception /*/public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {byte[] keyBytes = Base64.decodeBase64(privateKeyText);PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(RSA);Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, privateK);byte[] encryptedData = Base64.decodeBase64(text);int inputLen = encryptedData.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;byte[] cache;int i = 0;// 對數(shù)據(jù)分段解密while (inputLen - offSet > 0) {if (inputLen - offSet > MAX_DECRYPT_BLOCK) {cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);} else {cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * MAX_DECRYPT_BLOCK;}byte[] decryptedData = out.toByteArray();out.close();return new String(decryptedData, CHARSET_NAME);}/*** <p>* 公鑰加密* </p>** @param str 源數(shù)據(jù)* @param publicKey 公鑰(BASE64編碼)* @return* @throws Exception*/public static String encryptByPublicKey(String publicKey, String str)throws Exception {byte[] data = str.getBytes(CHARSET_NAME);byte[] keyBytes = Base64.decodeBase64(publicKey);X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(RSA);Key publicK = keyFactory.generatePublic(x509KeySpec);// 對數(shù)據(jù)加密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, publicK);int inputLen = data.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;byte[] cache;int i = 0;// 對數(shù)據(jù)分段加密while (inputLen - offSet > 0) {if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);} else {cache = cipher.doFinal(data, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * MAX_ENCRYPT_BLOCK;}byte[] encryptedData = out.toByteArray();out.close();return Base64.encodeBase64String(encryptedData);}/*** 構(gòu)建RSA密鑰對** @return /* @throws NoSuchAlgorithmException /*/public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);keyPairGenerator.initialize(1024);KeyPair keyPair = keyPairGenerator.generateKeyPair();RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());return new RsaKeyPair(publicKeyString, privateKeyString);}/*** RSA密鑰對對象*/public static class RsaKeyPair {private final String publicKey;private final String privateKey;public RsaKeyPair(String publicKey, String privateKey) {this.publicKey = publicKey;this.privateKey = privateKey;}public String getPublicKey() {return publicKey;}public String getPrivateKey() {return privateKey;}} }碰到的問題:
- rsa加密字符串的長度最大為117個(gè)字節(jié),所以前后端都必須使用分段加密和分段解密
- 如果加密內(nèi)容有中文,一定要先用js的encodeURIComponent方法或者java的URLEncoder.encode方法編碼后再加密,然后用js的decodeURIComponent方法或者java的URLDecoder.decode方法解密,否則直接對中文加解密時(shí)偶爾會(huì)失敗。
- 公鑰和私鑰對的生成一定要小心,我剛開始使用命令在linux上生成的公鑰和私鑰,但是前端能加密,后端不能解密,后來發(fā)現(xiàn)生成的密鑰對類型有問題,后端代碼無法用它來解密,然后改成了用后端代碼來生成密鑰對。
- 前端加解密方法中官方網(wǎng)站推薦把公鑰和私鑰都設(shè)置到JSEncrypt對象中,再進(jìn)行加解密,說這樣可以提高加解密的概率。但是我這樣設(shè)置了之后java后臺代碼無法解密,然后改成了前端加密就只設(shè)置公鑰,前端解密就只設(shè)置私鑰就正常了。
總結(jié)
以上是生活随笔為你收集整理的对app请求的参数和响应进行rsa加密和解密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: node.js setup wizard
- 下一篇: ModuleNotFoundError: