一、简介
开放接口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 param=new HashMap();
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 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);
}
测试结果:
图片
请求体内容被篡改了:
图片