一文教你如何使用AES對接口參數(shù)進行加密
前言
我們作為程序猿,在瀏覽網(wǎng)站的時候偶爾也會打開控制臺看看請求的接口,我們會發(fā)現(xiàn)有些接口的傳輸是 "亂碼" ,那么這個亂碼究竟是什么呢?為什么要這么做?
其實這個所謂的 "亂碼" 其實是一種加密后的密文,其原理是前后端提前約定好一種協(xié)議,在該協(xié)議下進行加解密的處理,例如:前端將數(shù)據(jù)加密后發(fā)送給后端,后端接收到參數(shù)后,第一時間先在約定好的協(xié)議下將密文解密成可識別的對象,再進行邏輯處理,最后將結(jié)果加密返回給前端,前端獲取到密文后,同樣依照約定好的協(xié)議對密文進行解密,最后將解密出來的數(shù)據(jù)拿來使用。
那么我們想實現(xiàn)同樣的效果,應(yīng)該如何做呢?別急,聽哥們給你一一道來。
介紹
一般來說加密算法會分為兩種:對稱加密算法 和 非對稱加密算法 。
對稱加密算法
摘自百度百科: 采用單鑰密碼系統(tǒng)的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。
非對稱加密算法
摘自百度百科: 不對稱加密算法使用兩把完全不同但又是完全匹配的一對鑰匙—公鑰和私鑰。在使用不對稱加密算法加密文件時,只有使用匹配的一對公鑰和私鑰,才能完成對明文的加密和解密過程。
經(jīng)過百度百科中的簡單概要,我們已經(jīng)知道了對稱加密算法
和 非對稱加密算法
都是什么,但它們中間又有什么不同呢?早就猜到你會這么問了,所以我已經(jīng)把它們的區(qū)別一一列出來了。
區(qū)別
密鑰
對稱加密: 一共只有一種密鑰,并將該密鑰同時用來加解密。
非對稱加密: 共有兩種密鑰:公鑰
和 私鑰
,使用公鑰來加密,使用私鑰來解密。
速度
對稱加密: 算法簡單且加解密容易,所以執(zhí)行效率高、速度快。
非對稱加密: 由于加密算法比較復(fù)雜,所以加解密的效率很低,速度遠不如 對稱加密
。
安全性
對稱加密: 由于加解密均使用的為同一個密鑰,那么若密鑰泄露則有被破解密文的風(fēng)險。
非對稱加密: 由于使用了兩種密鑰,且公鑰是可公開的密鑰,使用私鑰來進行解密,消除了用戶交換密鑰的條件,極大程度上保證了數(shù)據(jù)安全。
實現(xiàn)
在這里給大家介紹一下 AES + CBC + PKCS5Padding
的加密方式,具體實現(xiàn)如下:
引入依賴
<dependency> <groupId>org.apache.directory.studio</groupId> <artifactId>org.apache.commons.codec</artifactId> <version>1.8</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.13.0</version> </dependency>
編寫密鑰荷載
注意:這里的 AES_KEY
和 AES_IV
可以自定義,但 必須是16位的。
/** * @author Bummon * @description 荷載 * @date 2023-08-12 10:27 */ public class Common { /** * AES密鑰 */ public static final byte[] AES_KEY = "Ct9x5IUNHlhq0siZ".getBytes(); /** * AES偏移 */ public static final byte[] AES_IV = "MIIBIjANBgkqhkiG".getBytes(); }
編寫AES工具類
import com.test.constant.Common; import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * @author Bummon * @description AES工具類 * @date 2023-08-12 09:26 */ public class AESUtils { private static final String ALGORITHMSTR = "AES/CBC/PKCS5Padding"; /** * @param content 加密內(nèi)容 * @return {@link String} * @date 2023-08-12 09:27 * @author Bummon * @description 加密 */ public static String encrypt(String content) { String encode = null; try { Cipher cipher = initCipher(Cipher.ENCRYPT_MODE); byte[] encodeBytes = cipher.doFinal(content.getBytes()); encode = Base64.encodeBase64String(encodeBytes); } catch (Exception e) { e.printStackTrace(); } return encode; } public static String decrypt(String encryptStr) { String decode = null; try { Cipher cipher = initCipher(Cipher.DECRYPT_MODE); byte[] encodeBytes = Base64.decodeBase64(encryptStr); byte[] decodeBytes = cipher.doFinal(encodeBytes); decode = new String(decodeBytes); } catch (Exception e) { e.printStackTrace(); } return decode; } /** * @param cipherMode 操作類型 加密/解密 * @return {@link Cipher} * @date 2023-08-12 09:42 * @author Bummon * @description 初始化Cipher */ private static Cipher initCipher(int cipherMode) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); Cipher cipher = Cipher.getInstance(ALGORITHMSTR); SecretKeySpec keySpec = new SecretKeySpec(Common.AES_KEY, "AES"); IvParameterSpec ivParam = new IvParameterSpec(Common.AES_IV); cipher.init(cipherMode, keySpec, ivParam); return cipher; } public static void main(String[] args) { String encrypt = AESUtils.encrypt("Hello World"); String decrypt = AESUtils.decrypt(encrypt); System.out.println(encrypt); System.out.println(decrypt); } }
自定義注解
該注解作用于接口上,可以對接口的加密或者解密實現(xiàn)更加粒子化的控制,默認入?yún)⒔饷?,出參加密?/p>
import org.springframework.web.bind.annotation.Mapping; import java.lang.annotation.*; /** * @author Bummon * @description AES加解密注解 * @date 2023-08-12 09:44 */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Mapping @Documented public @interface AES { /** * 入?yún)⑹欠窠饷?,默認解密 */ boolean inDecode() default true; /** * 出參是否加密,默認加密 */ boolean outEncode() default true; }
DecodeRequestBodyAdvice
import com.test.anno.AES; import com.test.util.pwd.AESUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; /** * @author Bummon * @date 2023-08-12 10:22 * @description 請求數(shù)據(jù)解密 */ @Slf4j @ControllerAdvice(basePackages = "com.test.controller") public class DecodeRequestBodyAdvice implements RequestBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return true; } @Override public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return body; } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException { try { boolean encode = false; if (methodParameter.getMethod().isAnnotationPresent(AES.class)) { //獲取注解配置的包含和去除字段 AES aes = methodParameter.getMethodAnnotation(AES.class); //入?yún)⑹欠裥枰饷? encode = aes.decode(); } if (encode) { log.info("對方法method :【" + methodParameter.getMethod().getName() + "】返回數(shù)據(jù)進行解密"); return new MyHttpInputMessage(inputMessage); } else { return inputMessage; } } catch (Exception e) { e.printStackTrace(); log.error("對方法method :【" + methodParameter.getMethod().getName() + "】返回數(shù)據(jù)進行解密出現(xiàn)異常:" + e.getMessage()); return inputMessage; } } @Override public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) { return body; } class MyHttpInputMessage implements HttpInputMessage { private HttpHeaders headers; private InputStream body; public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception { this.headers = inputMessage.getHeaders(); String param = IOUtils.toString(inputMessage.getBody(), "UTF-8"); //去除請求數(shù)據(jù)中的轉(zhuǎn)義字符 String encryptStr = easpString(param).replace("\"", ""); String decrypt = AESUtils.decrypt(encryptStr); this.body = IOUtils.toInputStream(decrypt, "UTF-8"); } @Override public InputStream getBody() throws IOException { return body; } @Override public HttpHeaders getHeaders() { return headers; } /** * @param param * @return */ public String easpString(String param) { if (param != null && !param.equals("")) { String s = "{\"param\":"; //去除param中的轉(zhuǎn)義字符 String data = param.replaceAll("\\s*|\r|\n|\t", ""); if (!data.startsWith(s)) { throw new RuntimeException("參數(shù)【param】缺失異常!"); } else { int closeLen = data.length() - 1; int openLen = "{\"param\":".length(); String substring = StringUtils.substring(data, openLen, closeLen); return substring; } } return ""; } } }
EncodeResponseBodyAdvice
import com.fasterxml.jackson.databind.ObjectMapper; import com.test.anno.AES; import com.test.util.pwd.AESUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; 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.ResponseBodyAdvice; /** * @author Bummon * @date 2023-08-12 10:36 * @description 返回參數(shù)加密 */ @Slf4j @ControllerAdvice(basePackages = "com.test.controller") public class EncodeResponseBodyAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Class aClass) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { boolean encode = false; if (methodParameter.getMethod().isAnnotationPresent(AES.class)) { //獲取注解配置的包含和去除字段 AES aes = methodParameter.getMethodAnnotation(AES.class); //出參是否需要加密 encode = aes.encode(); } if (encode) { log.info("對方法method :【" + methodParameter.getMethod().getName() + "】返回數(shù)據(jù)進行加密"); ObjectMapper objectMapper = new ObjectMapper(); try { String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body); return AESUtils.encrypt(result); } catch (Exception e) { e.printStackTrace(); log.error("對方法method :【" + methodParameter.getMethod().getName() + "】返回數(shù)據(jù)進行解密出現(xiàn)異常:" + e.getMessage()); } } return body; } }
編寫測試控制器
import java.util.HashMap; import java.util.Map; /** * @author Bummon * @description * @date 2023-08-12 10:37 */ @RestController public class TestController { @AES(decode = false) @GetMapping("/getSecret") public Object getSecret() { Map<String, Object> map = new HashMap<>(); map.put("name", "Bummon"); map.put("homeUrl", "https://www.bummon.com/"); map.put("blogUrl", "https://blog.bummon.com/"); return map; } @AES(encode = false) @PostMapping("/getBySecret") public Object getBySecret(@RequestBody Map<String, Object> map) { return map; } }
我們在這里編寫了兩個接口,其中 getSecret
接口不對入?yún)⑦M行解密,對出參進行加密,也就是前端傳明文,后端返回為密文。getBySecret
接口是對入?yún)⑦M行解密,不對出參加密,也就是前端傳密文,后端返回為明文。
我們的測試思路就是先測試getSecret
接口,同時也獲取到了密文,在測試getBySecret
接口時將getSecret
接口返回的密文作為參數(shù)傳進去。
測試
我們通過getSecret
接口拿到了密文,接下來將該密文作為參數(shù)調(diào)用getBySecret
接口。
可以看到我們成功將密文解析了出來,并且對接口入?yún)]有影響。
到此這篇關(guān)于一文教你如何使用AES對接口參數(shù)進行加密的文章就介紹到這了,更多相關(guān)AES接口參數(shù)加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實例解析使用Java實現(xiàn)基本的音頻播放器的編寫要點
這篇文章主要介紹了使用Java實現(xiàn)基本的音頻播放器的代碼要點實例分享,包括音頻文件的循環(huán)播放等功能實現(xiàn)的關(guān)鍵點,需要的朋友可以參考下2016-01-01Spring數(shù)據(jù)庫連接池url參數(shù)踩坑及解決
這篇文章主要介紹了Spring數(shù)據(jù)庫連接池url參數(shù)踩坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09java連接postgresql數(shù)據(jù)庫代碼及maven配置方式
這篇文章主要介紹了java連接postgresql數(shù)據(jù)庫代碼及maven配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09MybatisPlus調(diào)用原生SQL的三種方法實例詳解
這篇文章主要介紹了MybatisPlus調(diào)用原生SQL的三種方法,在有些情況下需要用到MybatisPlus查詢原生SQL,MybatisPlus其實帶有運行原生SQL的方法,我這里列舉三種,需要的朋友可以參考下2022-09-09