研發必備:輕松玩轉開放接口API簽名和驗簽
一、簡介
開放接口API的簽名和驗簽是一種常見的安全機制,用于確保接口請求的完整性和真實性。
1.1、對稱加密和非對稱加密
對稱加密:加密和解密使用的是同一把密鑰。常用的對稱加密算法:DES,AES,3DES。
非對稱加密:加密和解密使用的是不同的密鑰,一把作為公開分享給加密方的叫做公鑰,另一把不分享作為解密的私鑰。公鑰加密的密文只有私鑰能進行解密;私鑰加密的密文也只有公鑰能進行解密。常見的非對稱加密算法:RSA,ECC。
總之:在效率上來說,對稱加密的效率顯然更高,但是非對稱加密的安全性更高。所以一般在實際的HTTPS加密過程中,首次連接使用的是公鑰加密算法(非對稱加密)來傳輸數據加密所要使用的對稱加密的密鑰,之后傳輸中使用的都是對稱加密算法。
1.2、生成非對稱秘鑰對
第三方系統作為調用方(客戶端),與接口服務方(服務端)約定好加密算法和客戶端名稱(clientID),便于在服務方系統中來唯一標識調用方系統。約定好以后,服務方為每一個調用方系統專門生成一個專屬的非對稱密鑰對(RSA密鑰對)。私鑰頒發給調用方系統(客戶端),公鑰由服務方持有。
圖片
注意:調用方(客戶端)系統需要保管好私鑰(存到調用方系統的后端)。因為對于服務方系統而言,調用方系統是消息的發送方,其持有的私鑰唯一標識了它的身份是服務方系統受信任的調用方。調用方系統的私鑰一旦泄露,調用方對原系統毫無信任可言。
1.3 開放接口API
不需要登錄憑證就允許被第三方系統調用的接口,必須要考慮接口數據的安全性問題。比如:數據是否被篡改?數據是否已過時?數據是否可以重復提交?等問題。為了防止開放接口被惡意調用,開放接口一般都需要驗簽才能被調用。
1.4、 簽名和驗簽
簽名:是第三方系統在調用接口API前,需按照接口API提供方的規則根據所有請求參數生成一個簽名(字符串),在調用接口時攜帶該簽名的。
特別注意:為了確保生成簽名的處理細節與服務方系統的驗簽邏輯是匹配的,服務方系統一般都提供jar包或者代碼片段給調用方來生成簽名,否則可能會因為一些處理細節不一致導致生成的簽名是無效的
驗簽:接口提供方會驗證簽名的有效性,只有簽名驗證有效才能正常調用接口,否則請求會被駁回。
圖片
二、應用案例
圖片
核心代碼:
/**
* @Description: TODO:使用RSA完成簽名驗簽
* @Author: yyalin
* @CreateDate: 2023/3/28 14:37
* @Version: V1.0
*/
@Slf4j
public class RSAUtils {
public static final String SIGNATURE_INSTANCE = "SHA256withRSA"; //簽名
public static final String KEYPAIR_INSTANCE = "RSA"; //秘鑰類型
/**
* 功能描述:RSA公私鑰生成器
* @MethodName: genKey
* @MethodParam: []
* @Return: Map
* @Author: yyalin
* @CreateDate: 2023/12/18 15:34
*/
public static Map genKey() throws Exception{
KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEYPAIR_INSTANCE);
kpg.initialize(1024);
KeyPair kep = kpg.generateKeyPair();
PrivateKey pkey = kep.getPrivate();
PublicKey pubkey = kep.getPublic();
Map<String,Object> param=new HashMap<String,Object>();
param.put("publicKey", new String(Base64Utils.encode(pubkey.getEncoded())));
param.put("privateKey", new String(Base64Utils.encode(pkey.getEncoded())));
return param;
}
/**
* 功能描述:RSA簽名
* @MethodName: sign
* @MethodParam: [content:需要簽名的字符串, privateKey:RSA私鑰]
* @Return: java.lang.String
* @Author: yyalin
* @CreateDate: 2023/12/18 16:10
*/
public static String sign(String content, String privateKey) throws Exception {
byte[] str= Base64Utils.decode(privateKey.getBytes("UTF-8"));
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(str);
KeyFactory keyf = KeyFactory.getInstance(KEYPAIR_INSTANCE);
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
java.security.Signature signature = java.security.Signature.getInstance(SIGNATURE_INSTANCE);
signature.initSign(priKey);
signature.update(content.getBytes("UTF-8"));
byte[] signed = signature.sign();
return new String(Base64Utils.encode(signed),"UTF-8");
}
/**
* 功能描述:RSA驗簽
* @MethodName: verify
* @MethodParam: [content:原文內容, sign:待驗證的簽名, public_key:RSA公鑰]
* @Return: boolean 簽名結果
* @Author: yyalin
* @CreateDate: 2023/12/18 16:11
*/
public static boolean verify(String content, String sign, String public_key)
throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(KEYPAIR_INSTANCE);
byte[] encodedKey = Base64Utils.decode(public_key.getBytes("UTF-8"));
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance(SIGNATURE_INSTANCE);
signature.initVerify(pubKey);
signature.update(content.getBytes("UTF-8"));
boolean bverify = signature.verify(Base64Utils.decode(sign.getBytes("UTF-8")));
return bverify;
}
}
測試內容:
//測試使用
public static void main(String[] args) throws Exception {
//1、獲取公私鑰匙 請求方獲取公鑰私鑰后,傳私鑰發送請求
Map<String,Object> param=RSAUtils.genKey();
log.info("輸出的公鑰私鑰param:"+param);
String publicKey= (String) param.get("publicKey");
String privateKey= (String) param.get("privateKey");
//2、簽名 獲取私鑰,獲取請求后對內容進行加標簽返回
String cnotallow="您好!";
String sign=RSAUtils.sign(content, privateKey);
log.info("使用私鑰輸出的標簽sign:"+sign);
//3、驗簽
// String cnotallow="您好!";
boolean verify=RSAUtils.verify(content, sign, publicKey);
log.info("使用公鑰驗簽結果verify:"+verify);
}
測試結果:
圖片
請求體內容被篡改了:
圖片