跳到主要内容

RSA2加签验签

描述

RSA算法是现今使用最广泛的秘钥密码算法,企连云采用RSA2算法对传输数据进行加签和验签

渠道与平台在调用企连云接口时,对请求参数使用RSA2算法私钥进行加签,接收到数据后,使用配对公钥进行验签,以防止数据在传输过程中被篡改,由此来鉴别请求来源的真伪。

使用方调用企连云接口

使用方在调用企连云接口时,必须对接口进行签名,否则接口会出现签名异常

签名计算规则如下:

1.对参与签名计算的六个参数(app_id、biz_content、charset、method、utc_timestamp、version),按照首字符的键值ASCII码进行递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

2.将排序号的参数用&连接

3.对拼接好的参数进行加签

请求参数如下:

charset=UTF-8
biz_content={"name":"lisi"}
utc_timestamp=1563004103212
app_id=23432423422
version=1.0.0
method=c.localLife.dining.getPoiList

排序后待加签字符串如下:

app_id=23432423422&biz_content={"name":"lisi"}&charset=UTF-8&method=c.localLife.dining.getPoiList&utc_timestamp=1563004103212&version=1.0.0

加签后参数如下:

charset=UTF-8
biz_content={"name":"lisi"}
utc_timestamp=1563004103212
app_id=23432423422
version=1.0.0
method=c.localLife.dining.getPoiList
sign_type=RSA2
sign=OLBN2GfF/4RkaXEV1VT5v6Ce/EGj8T+vxBqUCVwuX0fG6zo2YArz/RDAjJFdu1PtwP/ENgYVOgGvkWinH6bZWfvP9caeej5CGNTgfGHVtFTANXO10bgvAOg28lzP+qo3xC0tozEvc/uPj2WBcRtoERfPomI+rGHLLUdq2xLmZJpo6+QoQOE3k0MFusXwGa8xT8v+G7SIT0GCyOgF8fOg==### ###

注:加签后调用企连云请求新增两个参数:sign(加签结果)、sign_type(默认为RSA2)

企连云调用提供方接口

提供方有提供接口调用规则

提供方有提供标准化接口,请给出接口示例,企连云会根据提供方的接口规则进行对接

提供方没有提供接口调用规则

提供方没有标准化接口,需要按照企连云的接口规则进行对接,提供方可以根据自己的实际情况决定是否对请求进行验签。

企连云调用提供方接口时,加签逻辑、参数和使用方调用企连云的加签、参数均保存一致,详细请参考文档中的“使用方调用企连云接口”。

代码示例

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
* @Classname GenerateSignUtils
* @Description 签名 工具类
* @Date 2024/8/24 10:09
* @Created by noway
*/
public class GenerateSignUtils {

/**
* 加签
* @param pramMap
* @param privateKey
* @return
* @throws Exception
*/
public static String generateSign(Map<String,String> pramMap,String privateKey) throws Exception {
String signContent = signContent(pramMap);
return sign(signContent,privateKey);
}

public static boolean checkSign(Map<String,String> parmMap,String sign, String publicKey) throws Exception {
String signContent= signContent(parmMap);
return checkSign(signContent,sign,publicKey);
}

/**
* 验签
* @param content
* @param sigin
* @param publicKey
* @return
* @throws Exception
*/
public static boolean checkSign(String content,String sigin,String publicKey) throws Exception {
try {
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
StringWriter writer= new StringWriter();
io(new InputStreamReader(new ByteArrayInputStream(publicKey.getBytes())),writer);
byte[] encodedKey = writer.toString().getBytes();
encodedKey = org.apache.commons.codec.binary.Base64.decodeBase64(encodedKey);
PublicKey pubKey= keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
Signature signature= Signature.getInstance("SHA256WithRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes("UTF-8"));
return signature.verify(Base64.getDecoder().decode(sigin.getBytes()));
}catch (Exception e){
throw new Exception(e);
}

}


/**
* 参数转换为待加签字符串
* @param parmMap
* @return
*/
private static String signContent(Map<String,String> parmMap){
StringBuilder content = new StringBuilder();
List<String> keys= new ArrayList<>(parmMap.keySet());
Collections.sort(keys);
for(int i=0;i<keys.size();i++){
String key = keys.get(i);
if ("sign_type".equals(key)||"sign".equals(key)||"need_encrypt".equals(key)){
continue;
}
String vale= parmMap.get(key);
if (key!=null && !"".equalsIgnoreCase(key) && vale!=null && !"".equalsIgnoreCase(vale)){
content.append(i==0? "" : "&").append(key).append("=").append(vale);
}
}
return content.toString();
}

/**
* 签名参数
* @param signContent
* @param privateKey
* @return
* @throws Exception
*/
public static String sign(String signContent,String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodeKey = readText(new ByteArrayInputStream(privateKey.getBytes())).getBytes();
encodeKey = Base64.getDecoder().decode(encodeKey);
PrivateKey priKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodeKey));

Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(priKey);
signature.update(signContent.getBytes("UTF-8"));
byte[] signed = signature.sign();
return new String(Base64.getEncoder().encode(signed));
}



/**
*
* @param in
* @param out
* @throws Exception
*/
private static void io(Reader in,Writer out) throws Exception {
try {
int bufferSize = 4096;
char[] buffer = new char[bufferSize];
int amount;
while ((amount = in.read(buffer)) >=0){
out.write(buffer,0,amount);
}
}catch (Exception e){
throw new Exception(e);
}

}

/**
* 读取私钥流
* @param in
* @return
* @throws Exception
*/
private static String readText(InputStream in) throws Exception {
try {
Reader reader = new InputStreamReader(in);
StringWriter writer = new StringWriter();
int bufferSize =4096;
char[] buffer = new char[bufferSize];
int amount;
while ((amount = reader.read(buffer)) >=0){
writer.write(buffer,0,amount);
}
return writer.toString();
}catch (Exception e){
throw new Exception(e);
}
}


public static void main(String[] args) throws Exception {
String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCf0grn6Sp61CQlDsNRA31Kjew09rDagMMRqo/EI7qDGDvJmExUZ0OaCQh7AEhg5GRGhBiY7xur3cC2swFd+AKlFTeNtgN4KYsLhPwrOiE029HA0Xg2WlkeAnJCsPCAgK6VuK9vV7dZafbfASkw3+cV+A9tuTBtyMqQJ7tDdQEzhzn7RhqkWIt8IboyAn+sGhwqXw20l1DVEpg1f0+DGRjwRGg53l9QJ3PTugxoNPkW3lDrOIgQmbKHmjE2Qaow0wgTxOKXb6PWlToI6lwoxrWZDRATDNNwt1tVcPW99C54fcH5oJbViOtLYZOc6lCenXd6v5VBhHE23nLcWBSbYHc5AgMBAAECggEBAIiiTPqZx9x8023vIiJ3xjzf+soJAYe8v4ix9fksxn7fT/X4w4EBS7U79ckao8ZaXDRBKLpO6axUiDZp8UhCCRjTe8wmiO7JLXuqtN/L8am8KKYdLxXgxgG2+7JyjRp/aULNQwcAydqikhFzb6C+O1T331DiGzowiKUXoqx4Mo+2smBZOfgg2uUSlgTB2djtNj9hmL6blFIky3UwayQXNR079FverCOCY9Buhnxks4IN6RVz/RqQRpCN5ZwijrlvzmkBxPLPfxGI62mlqEvemObv4NkD5A3EEWIebTph8gmned1oXEijz5TRiQn2TaG76cwzI2Fg8aaSuPxWP1a0nyUCgYEA99h+RoisBf2m27iuxyzVUymL4Ah7rYYVvqmCiOlMcS80RmkLw6tHJz01ZlARMhUJbaYQBTuiCT+T9ElWRPBpckAUtyA0B9tMPH0pRf6oQc+Kvf9QXFVUhWMRBm6FzchfxhtTifrF6eaW282SLobDT9mnh+B2Ej4tUA+z/qEwVGcCgYEApRQlzuJQkHzQiwlmOP5waz5MPDJIFN6JuGqoGt/zg6SQwfP462aY8VEzCV3fNueUljO9my2LzapikaBo6IZ+r5aP+x9XH++bTWRrOsYl+oRoVlvZ1I9Am7MTaRqk3KFTIPqT76F89MVmS5SNdec/oZmRqhKgb0RV69L7VDn2k18CgYEA0Ke8t5j8aha/0R9MVXaTKe5CShwaM149FEUVFjqDFo7NDbIGK3cY8hn/yOEDeQxo4Zz6w80Of4a9At5y+JsFyx/T0NAVvb+MESi2BK197dnSTl6Rwwo7nAhpHucRUsushdYfoHw0/tQYkXgh53WhHKp69lOsU3NxJoPx3x7I12UCgYBRhoe9iotbMCkV4Uh4sT/31sZznCl5FQZS62mmDCAJTYaoIsM2Dm9ODln0MEYCqY/6NgdX0cCPi6wuW1g0Lef285ab4Lh4by71o7hJSH6NIEMfEzGbBjxUKLZXW+87fvi8+sOAvvIlOC/y96R9K/2C44LKyXQuP0NnucShrhaWZQKBgQCSYWfpWs5uTSvXeY9K/4MZx+s/+3N2802CHh/Z6VCvPtIioHprUIox+W2IPOz3qaQ5gWXLl+M6Wa9Jq6S4HuPO2sUvvw2n/xbB4viBwtYG+PNIGC4SgBElr/mC4FFtawYXfsoLN8S/YJ25EL0uA/BF5FubQq1f7X4G6sp3zlenyw==";
String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn9IK5+kqetQkJQ7DUQN9So3sNPaw2oDDEaqPxCO6gxg7yZhMVGdDmgkIewBIYORkRoQYmO8bq93AtrMBXfgCpRU3jbYDeCmLC4T8KzohNNvRwNF4NlpZHgJyQrDwgICulbivb1e3WWn23wEpMN/nFfgPbbkwbcjKkCe7Q3UBM4c5+0YapFiLfCG6MgJ/rBocKl8NtJdQ1RKYNX9PgxkY8ERoOd5fUCdz07oMaDT5Ft5Q6ziIEJmyh5oxNkGqMNMIE8Til2+j1pU6COpcKMa1mQ0QEwzTcLdbVXD1vfQueH3B+aCW1YjrS2GTnOpQnp13er+VQYRxNt5y3FgUm2B3OQIDAQAB";


Map<String ,String> paramMap = new HashMap<String,String>();

// 构造业务参数
JSONObject bizContent = new JSONObject();
bizContent.put("source", "example_api");
bizContent.put("merchantId", "8888777711119999");
bizContent.put("shopInfos", new String[]{});
paramMap.put("biz_content", JSON.toJSONString(bizContent));

// 构造参与加签的公共参数
paramMap.put("app_id", "202210130194031937");
paramMap.put("utc_timestamp", String.valueOf(System.currentTimeMillis()));
paramMap.put("version", "1.0.0");
paramMap.put("charset", "UTF-8");
paramMap.put("method", "c.localLife.dining.getPoiList");
paramMap.put("sign", generateSign(paramMap,privateKey));
paramMap.put("sign_type", "RSA2");
System.out.println("加签==>"+paramMap);

boolean checkSignResult = checkSign(paramMap,generateSign(paramMap,privateKey),publicKey);
System.out.println("验签==>"+checkSignResult);


}




}

签名算法明细

签名算法标准签名算法描述
RSA2SHA256WithRSA强制要求 RSA 密钥的长度至少为 2048。

1.请求参数中除了六个固定的参数外,其他的参数均不参与签名生成,这六个参数分别为app_id、biz_content、charset、method、utc_timestamp、version。将参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

2.拼接排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。

3.把拼装好的待签名字符串采用utf-8编码,使用签名RSA2算法对编码后的字节流进行处理,再用Base64算法对处理后的字节流进行编码,生成的字符串即为签名结果