Java http加簽、驗簽實現(xiàn)方案詳解
為什么要加密驗簽? 防止報文明文傳輸
數(shù)據(jù)在網(wǎng)絡(luò)傳輸過程中,容易被抓包。如果使用的是HTTP協(xié)議的請求/響應(yīng)(Request OR Response),它是明文傳輸?shù)?,都是可以被截獲、篡改、重放(重發(fā))的。所以需要進行數(shù)據(jù)的加密驗簽,所以需要考慮以下幾點。
- 防偽裝攻擊(案例:在公共網(wǎng)絡(luò)環(huán)境中,第三方 有意或惡意 的調(diào)用我們的接口)
- 防篡改攻擊(案例:在公共網(wǎng)絡(luò)環(huán)境中,請求頭/查詢字符串/內(nèi)容 在傳輸過程被修改)
- 防重放攻擊(案例:在公共網(wǎng)絡(luò)環(huán)境中,請求被截獲,稍后被重放或多次重放)
- 防數(shù)據(jù)信息泄漏(案例:截獲用戶登錄請求,截獲到賬號、密碼等)
實現(xiàn)方式
常見的方式,就是對關(guān)鍵字段加密。比如查詢訂單接口,就可以對訂單號進行加密。一般常用的加密算法對稱加密算法(如:AES),或者哈希算法處理(如:MD5)
對稱加密:加密和解密使用相同秘鑰的加密算法
采用單鑰密碼系統(tǒng)的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。
非對稱加密:非對稱加密算法需要兩個密鑰(公開密鑰和私有密鑰)。公鑰和私鑰是成對存在的,如果用公鑰對數(shù)據(jù)加密,只有對應(yīng)的私鑰才能解密。 (非對稱加密是更安全的做法,加密是算法RSA或SM2)
非對稱加密算法需要兩個密鑰來進行加密和解密,這兩個密鑰是公開密鑰(public key,簡稱公鑰)和私有密鑰(private key,簡稱私鑰)。
加簽驗簽:使用Hash算法(如 MD5或者SHA-256)把原始請求參數(shù)生成報文摘要,然后用私鑰對這個摘要進行加密,得到報文對應(yīng)的sign
加簽:用Hash函數(shù)把原始報文生成報文摘要,然后用私鑰對這個摘要進行加密,就得到這個報文對應(yīng)的數(shù)字簽名。通常來說呢,請求方會把「數(shù)字簽名和報文原文」一并發(fā)送給接收方。
驗簽:接收方拿到原始報文和數(shù)字簽名后,用「同一個Hash函數(shù)」從報文中生成摘要A。另外,用對方提供的公鑰對數(shù)字簽名進行解密,得到摘要B,對比A和B是否相同,就可以得知報文有沒有被篡改過。
客戶端操作
請求參數(shù):
字段 | 類型 | 必傳 | 說明 |
---|---|---|---|
sign | String | 是 | 接口簽名,用戶接口驗證 |
app_id | String | 是 | 開放平臺的APP_ID,例如:1234 |
date_time | String | 是 | 當前時間戳 |
key | String | 是 | 開發(fā)平臺的APP_KEY,例如:XA12#Da |
name | String | 是 | 業(yè)務(wù)參數(shù) |
age | String | 是 | 業(yè)務(wù)參數(shù) |
業(yè)務(wù)參數(shù)消息體數(shù)據(jù)格式:Content-Type 指定為 application/json
1.將請求參數(shù)中除sign外的多個鍵值對,根據(jù)鍵按照字典序排序,并按照"key1=value1&key2=value2…"的格式拼成一個字符串
String sortStr=" age=11&app_id=1234&date_time=1656926899731&name=xxx"
2.將key拼接在第一步中排序后的字符串后面得到待簽名字符串
String sortStr ="age=11&app_id=1234&date_time=1656926899731&name=xxxkey=XA12#Da"
3.使用md5算法加密待加密字符串并轉(zhuǎn)為大寫即為sign
String sign ="57A132B7585F77B1948812275BE945B8"
4.將sign添加到請求參數(shù)中
https://www.baidu.com/test/get?age=11&app_id=1234&date_time=1656926899731&name=xxx&sign=57A132B7585F77B1948812275BE945B8
需要注意以下重要規(guī)則:
◆ 請求參數(shù)中有中文時,中文需要經(jīng)過url編碼,但計算簽名時不需要;
◆ 請求參數(shù)的值為空則不參與簽名;
◆ 參數(shù)名區(qū)分大小寫;
◆ sign參數(shù)不參與簽名;
服務(wù)端操作
1.接收到請求參數(shù),轉(zhuǎn)JSON格式
2.驗簽
2.1拿出用戶簽名
2.2根據(jù)APP_ID 拿去數(shù)據(jù)庫中的KEY,使用該KEY進行重簽參數(shù)
2.3如果重簽結(jié)果和用戶簽名一致則通過,否則返回簽名錯誤
2.4校驗參數(shù)中的時間戳,如果時間戳 超過當前時間5分鐘則簽名失效
3.如果c、d都通過則正常請求業(yè)務(wù)
package com.chinaunicom.utils; import cn.hutool.crypto.SecureUtil; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.util.*; /** * @author yming wang * @date 2024/3/4 13:48 * @desc */ @Slf4j public class SignUtil { /** * sign有效期 */ private static final int TIMES = 111 * 60 * 1000; public static boolean check(JSONObject params, String appKey, String sign) { try { //公鑰驗簽 sign = RsaUtils.decryptByPublicKey(sign, appKey); if (!sign.equals(getSign(params, appKey))) { log.info("簽名內(nèi)容正確"); return false; } Long expireTime = params.getLong("timestamp"); Long currTime = System.currentTimeMillis(); if ((currTime - expireTime) < 0 || (currTime - expireTime) > TIMES) { log.info("簽名時間已過期"); return false; } log.info("驗簽成功"); return true; } catch (Exception e) { log.error("驗簽發(fā)生異常:", e); } return false; } /** * @param params * @return java.lang.String * @params: * @author yming wang * @date 2024/3/4 14:44 * @desc 加簽算法: 原始報文 ---hash算法---> 消息摘要 ---RSA私鑰加密---> 數(shù)字簽名 * 驗簽算法:數(shù)字簽名 ---RSA公鑰解密--> 消息摘要 ---> 根據(jù)參數(shù)重新摘要 ---> 對比摘要喜喜 */ public static String getSign(JSONObject params, String appKey) { //將參數(shù)進行升序 String sortParams = sortParams(params, appKey); //將參數(shù)進行hash生成消息摘要 String sign = SecureUtil.md5(sortParams); return sign; } /** * @param params * @param appKey * @return java.lang.String * @params: * @author yming wang * @date 2024/3/4 15:25 * @desc 將參數(shù)進行升序 */ public static String sortParams(JSONObject params, String appKey) { List<Map.Entry<String, Object>> entries = new ArrayList<>(params.entrySet()); Collections.sort(entries, Comparator.comparing(Map.Entry::getKey)); StringBuffer str = new StringBuffer(); for (Map.Entry<String, Object> entry : entries) { Object value = entry.getValue(); if (value != null && StringUtils.isNotBlank(value.toString())) { str.append(entry.getKey()); str.append("="); str.append(value); str.append("&"); } } //md5加上鹽值避免根絕request body參數(shù)生成sign str.append("appKey"); str.append("="); str.append(appKey); return str.toString(); } public static void main(String[] args) throws Exception { String privateKey = "privateKey "; String publicKey = "publicKey "; JSONObject data = new JSONObject(); data.put("appId", "10002"); data.put("username", "用戶名"); data.put("account", "用戶賬號"); String pwd = RsaUtils.encryptByPrivateKey("用戶密碼", privateKey); log.info("encryptPwd:{}", pwd); data.put("password", pwd); long timestamp = System.currentTimeMillis(); data.put("timestamp", timestamp); //消息摘要 String sign = getSign(data, publicKey); log.info("timestamp:{}", timestamp); log.info("isTrue:{}", sign.equals(getSign(data, publicKey))); log.info("消息摘要:{}", sign); //生成數(shù)字證書 sign = RsaUtils.encryptByPrivateKey(sign, privateKey); log.info("生成數(shù)字證書:{}", sign); log.info("打印請求參數(shù):{}", data); log.info("驗簽:{}", check(data, publicKey, sign)); } }
package com.chinaunicom.utils; import lombok.extern.slf4j.Slf4j; import javax.crypto.Cipher; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.HashMap; import java.util.Map; /** * @program: CSDN * @description: yming wang * @author: wyming * @create: 2021-06-08 09:30:14 **/ @Slf4j public class RsaUtils { /** * 簽名算法名稱 */ private static final String RSA_KEY_ALGORITHM = "RSA"; /** * 標準簽名算法名稱 */ private static final String RSA_SIGNATURE_ALGORITHM = "SHA1withRSA"; private static final String RSA2_SIGNATURE_ALGORITHM = "SHA256withRSA"; /** * RSA密鑰長度,默認密鑰長度是1024,密鑰長度必須是64的倍數(shù),在512到65536位之間,不管是RSA還是RSA2長度推薦使用2048 */ private static final int KEY_SIZE = 2048; /** * 生成密鑰對 * * @return 返回包含公私鑰的map */ public static Map<String, String> generateKey() { KeyPairGenerator keygen; try { keygen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("RSA初始化密鑰出現(xiàn)錯誤,算法異常"); } SecureRandom secrand = new SecureRandom(); //初始化隨機產(chǎn)生器 secrand.setSeed("Alian".getBytes()); //初始化密鑰生成器 keygen.initialize(KEY_SIZE, secrand); KeyPair keyPair = keygen.genKeyPair(); //獲取公鑰并轉(zhuǎn)成base64編碼 byte[] pub_key = keyPair.getPublic().getEncoded(); String publicKeyStr = Base64.getEncoder().encodeToString(pub_key); //獲取私鑰并轉(zhuǎn)成base64編碼 byte[] pri_key = keyPair.getPrivate().getEncoded(); String privateKeyStr = Base64.getEncoder().encodeToString(pri_key); //創(chuàng)建一個Map返回結(jié)果 Map<String, String> keyPairMap = new HashMap<>(); keyPairMap.put("publicKeyStr", publicKeyStr); keyPairMap.put("privateKeyStr", privateKeyStr); return keyPairMap; } /** * 公鑰加密(用于數(shù)據(jù)加密) * * @param data 加密前的字符串 * @param publicKeyStr base64編碼后的公鑰 * @return base64編碼后的字符串 * @throws Exception */ public static String encryptByPublicKey(String data, String publicKeyStr) throws Exception { //Java原生base64解碼 byte[] pubKey = Base64.getDecoder().decode(publicKeyStr); //創(chuàng)建X509編碼密鑰規(guī)范 X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey); //返回轉(zhuǎn)換指定算法的KeyFactory對象 KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM); //根據(jù)X509編碼密鑰規(guī)范產(chǎn)生公鑰對象 PublicKey publicKey = keyFactory.generatePublic(x509KeySpec); //根據(jù)轉(zhuǎn)換的名稱獲取密碼對象Cipher(轉(zhuǎn)換的名稱:算法/工作模式/填充模式) Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); //用公鑰初始化此Cipher對象(加密模式) cipher.init(Cipher.ENCRYPT_MODE, publicKey); //對數(shù)據(jù)加密 byte[] encrypt = cipher.doFinal(data.getBytes()); //返回base64編碼后的字符串 return Base64.getEncoder().encodeToString(encrypt); } /** * 私鑰解密(用于數(shù)據(jù)解密) * * @param data 解密前的字符串 * @param privateKeyStr 私鑰 * @return 解密后的字符串 * @throws Exception */ public static String decryptByPrivateKey(String data, String privateKeyStr) throws Exception { //Java原生base64解碼 byte[] priKey = Base64.getDecoder().decode(privateKeyStr); //創(chuàng)建PKCS8編碼密鑰規(guī)范 PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey); //返回轉(zhuǎn)換指定算法的KeyFactory對象 KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM); //根據(jù)PKCS8編碼密鑰規(guī)范產(chǎn)生私鑰對象 PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec); //根據(jù)轉(zhuǎn)換的名稱獲取密碼對象Cipher(轉(zhuǎn)換的名稱:算法/工作模式/填充模式) Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); //用私鑰初始化此Cipher對象(解密模式) cipher.init(Cipher.DECRYPT_MODE, privateKey); //對數(shù)據(jù)解密 byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(data)); //返回字符串 return new String(decrypt); } /** * 私鑰加密(用于數(shù)據(jù)簽名) * * @param data 加密前的字符串 * @param privateKeyStr base64編碼后的私鑰 * @return base64編碼后后的字符串 * @throws Exception */ public static String encryptByPrivateKey(String data, String privateKeyStr) throws Exception { //Java原生base64解碼 byte[] priKey = Base64.getDecoder().decode(privateKeyStr); //創(chuàng)建PKCS8編碼密鑰規(guī)范 PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey); //返回轉(zhuǎn)換指定算法的KeyFactory對象 KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM); //根據(jù)PKCS8編碼密鑰規(guī)范產(chǎn)生私鑰對象 PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec); //根據(jù)轉(zhuǎn)換的名稱獲取密碼對象Cipher(轉(zhuǎn)換的名稱:算法/工作模式/填充模式) Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); //用私鑰初始化此Cipher對象(加密模式) cipher.init(Cipher.ENCRYPT_MODE, privateKey); //對數(shù)據(jù)加密 byte[] encrypt = cipher.doFinal(data.getBytes()); //返回base64編碼后的字符串 return Base64.getEncoder().encodeToString(encrypt); } /** * 公鑰解密(用于數(shù)據(jù)驗簽) * * @param data 解密前的字符串 * @param publicKeyStr base64編碼后的公鑰 * @return 解密后的字符串 * @throws Exception */ public static String decryptByPublicKey(String data, String publicKeyStr) throws Exception { //Java原生base64解碼 byte[] pubKey = Base64.getDecoder().decode(publicKeyStr); //創(chuàng)建X509編碼密鑰規(guī)范 X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey); //返回轉(zhuǎn)換指定算法的KeyFactory對象 KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM); //根據(jù)X509編碼密鑰規(guī)范產(chǎn)生公鑰對象 PublicKey publicKey = keyFactory.generatePublic(x509KeySpec); //根據(jù)轉(zhuǎn)換的名稱獲取密碼對象Cipher(轉(zhuǎn)換的名稱:算法/工作模式/填充模式) Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); //用公鑰初始化此Cipher對象(解密模式) cipher.init(Cipher.DECRYPT_MODE, publicKey); //對數(shù)據(jù)解密 byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(data)); //返回字符串 return new String(decrypt); } /** * RSA簽名 * * @param data 待簽名數(shù)據(jù) * @param priKey 私鑰 * @param signType RSA或RSA2 * @return 簽名 * @throws Exception */ public static String sign(byte[] data, byte[] priKey, String signType) throws Exception { //創(chuàng)建PKCS8編碼密鑰規(guī)范 PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey); //返回轉(zhuǎn)換指定算法的KeyFactory對象 KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM); //根據(jù)PKCS8編碼密鑰規(guī)范產(chǎn)生私鑰對象 PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec); //標準簽名算法名稱(RSA還是RSA2) String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM; //用指定算法產(chǎn)生簽名對象Signature Signature signature = Signature.getInstance(algorithm); //用私鑰初始化簽名對象Signature signature.initSign(privateKey); //將待簽名的數(shù)據(jù)傳送給簽名對象(須在初始化之后) signature.update(data); //返回簽名結(jié)果字節(jié)數(shù)組 byte[] sign = signature.sign(); //返回Base64編碼后的字符串 return Base64.getEncoder().encodeToString(sign); } /** * RSA校驗數(shù)字簽名 * * @param data 待校驗數(shù)據(jù) * @param sign 數(shù)字簽名 * @param pubKey 公鑰 * @param signType RSA或RSA2 * @return boolean 校驗成功返回true,失敗返回false */ public static boolean verify(byte[] data, byte[] sign, byte[] pubKey, String signType) throws Exception { //返回轉(zhuǎn)換指定算法的KeyFactory對象 KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM); //創(chuàng)建X509編碼密鑰規(guī)范 X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey); //根據(jù)X509編碼密鑰規(guī)范產(chǎn)生公鑰對象 PublicKey publicKey = keyFactory.generatePublic(x509KeySpec); //標準簽名算法名稱(RSA還是RSA2) String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM; //用指定算法產(chǎn)生簽名對象Signature Signature signature = Signature.getInstance(algorithm); //用公鑰初始化簽名對象,用于驗證簽名 signature.initVerify(publicKey); //更新簽名內(nèi)容 signature.update(data); //得到驗證結(jié)果 return signature.verify(sign); } public static void demo() throws Exception { Map<String, String> stringStringMap = generateKey(); String publicKeyStr = stringStringMap.get("publicKeyStr"); String privateKeyStr = stringStringMap.get("privateKeyStr"); System.out.println("-----------------生成的公鑰和私鑰------------------------------"); System.out.println("獲取到的公鑰:" + publicKeyStr); System.out.println("獲取到的私鑰:" + privateKeyStr); // 待加密數(shù)據(jù) String data = "tranSeq=1920542585&amount=100&payType=wechat"; // 公鑰加密 System.out.println("---------公鑰--------加密和解密------------------------------"); System.out.println("待加密的數(shù)據(jù):" + data); String encrypt = RsaUtils.encryptByPublicKey(data, publicKeyStr); System.out.println("加密后數(shù)據(jù):" + encrypt); // 私鑰解密 String decrypt = RsaUtils.decryptByPrivateKey(encrypt, privateKeyStr); System.out.println("解密后數(shù)據(jù):" + decrypt); // 私鑰加密 System.out.println("----------私鑰-------加密和解密------------------------------"); System.out.println("待加密的數(shù)據(jù):" + data); encrypt = RsaUtils.encryptByPrivateKey(data, privateKeyStr); System.out.println("加密后數(shù)據(jù):" + encrypt); // 私鑰解密 decrypt = RsaUtils.decryptByPublicKey(encrypt, publicKeyStr); System.out.println("解密后數(shù)據(jù):" + decrypt); } }
到此這篇關(guān)于Java http加簽、驗簽實現(xiàn)方案的文章就介紹到這了,更多相關(guān)Java http加簽、驗簽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot登錄攔截器+ThreadLocal實現(xiàn)用戶信息存儲的實例代碼
ThreadLocal 為變量在每個線程中創(chuàng)建了一個副本,這樣每個線程都可以訪問自己內(nèi)部的副本變量,這篇文章主要介紹了springboot登錄攔截器+ThreadLocal實現(xiàn)用戶信息存儲的實例代碼,需要的朋友可以參考下2024-03-03Spring Boot整合Mybatis并完成CRUD操作的實現(xiàn)示例
這篇文章主要介紹了Spring Boot整合Mybatis并完成CRUD操作的實現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12SpringBoot使用JWT實現(xiàn)登錄驗證的方法示例
這篇文章主要介紹了SpringBoot使用JWT實現(xiàn)登錄驗證的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-06-06RestTemplate使用之如何設(shè)置請求頭、請求體
這篇文章主要介紹了RestTemplate使用之如何設(shè)置請求頭、請求體問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07