技術(shù)術(shù)語
概念和使用(如圖)
工保網(wǎng)公鑰是平臺生成的螃壤,平臺自己保存私鑰脱羡,開發(fā)者保存公鑰,主要作用是:讓開發(fā)者驗(yàn)證工保網(wǎng)申請接口相關(guān)數(shù)據(jù)
開發(fā)者公鑰是開發(fā)者生成的蜈抓,開發(fā)者自己保存私鑰鸯绿,公鑰提交到工保網(wǎng)跋破,主要作用是:讓工保網(wǎng)校驗(yàn)來自開發(fā)者的回調(diào)數(shù)據(jù)
限定
基本限定
1.統(tǒng)一字符集:UTF-8 ; 2.請求接口的參數(shù)列表均為字符串類型鍵值對瓶蝴;
簽名規(guī)則
1.排除參數(shù)列表中名為 sign的參數(shù)毒返; 2.將剩余參數(shù)按參數(shù)名字典序正序排列; 3.將參數(shù)與其對應(yīng)的值使用 “” 連接舷手,組成參數(shù)字符串拧簸; 4.將待簽名字符串和業(yè)務(wù)方私鑰使用 SHA256withRSA 簽名算法得出最終簽名。
支持的簽名算法
名稱 | 標(biāo)準(zhǔn)簽名算法名稱 | 描述 |
---|---|---|
RSA2 | SHA256WithRSA | 強(qiáng)制要求 RSA 密鑰的長度至少為 2048 |
注意:參與加簽數(shù)據(jù)可能是全量字段(動態(tài)字段)建議驗(yàn)簽使用JSON或者M(jìn)ap接受數(shù)據(jù)進(jìn)行驗(yàn)簽
驗(yàn)簽加簽Demo??點(diǎn)擊下載
簽名計(jì)算過程示例
使用密鑰生成中的示例公私鑰來做計(jì)算演示男窟。
1.初始請求業(yè)務(wù)參數(shù)
{
"body":{
"applicantInfo":{
"applicantName":"工迸璩啵科技2",
"contactName":"劉備",
"contactPhone":"18325648012",
"identifyNumber":"91330100MA2KH64H27",
"insuredAddress":"網(wǎng)商路18號",
"managerId":"340824199001012223",
"managerName":"劉備"
},
"fileInfoList":{
"file":[
{
"fileName":"iEfp.jpg",
"filePath":"http://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg"
}
]
},
"insuredInfo":{
"contactName":"關(guān)羽",
"contactPhone":"18889901122",
"identifyAdd":"19號",
"identifyNumber":"340822199100902211",
"insuredName":"阿里巴巴"
},
"policyInfo":{
"endDate":"2021-11-13 23:59:59",
"insuranceDays":"120",
"orderNo":"1406911003941158914",
"areaCode":"330100000000",
"rate":"0.25",
"riskCode":"TB",
"specialClause":"1",
"startDate":"2021-07-17 00:00:00",
"sumInsured":"10000.0",
"sumPremium":"1.0"
},
"projectInfo":{
"engineeringAddress":"網(wǎng)商路18號",
"engineeringContractNum":"gyx199001",
"engineeringCost":"10000",
"planProjectEndDate":"2021-07-31 16:13:35",
"planProjectStartDate":"2021-07-29 16:13:35",
"projectCode":"AYC1000",
"projectTime":"120",
"projectType":"01",
"tenderName":"關(guān)羽",
"tenderProjectName":"希望工程"
}
}
}
2.生成待簽名字符串
工奔指唬科技2劉備1832564801291330100MA2KH64H27網(wǎng)商路18號340824199001012223劉備iEfp.jpghttp://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg關(guān)羽1888990112219號340822199100902211阿里巴巴3301000000002021-11-13 23:59:5912014069110039411589140.25TB12021-07-17 00:00:0010000.01.0網(wǎng)商路18號gyx199001100002021-07-31 16:13:352021-07-29 16:13:35AYC100012001關(guān)羽希望工程
3.生成最終簽名串
mvzq0+Hn6bBkKAWaH3hJ4hDsQK/KxzxfzQVMDGigdfif+TERSnKvkoPiQtGikE2GjaVUoR8Td73mT4RSbk9H0PS0fPk9Twf//R4K/TKA8zlWq+QfI6OyKfoQmZmBNc1jEHqI21EzcP8EHsCgz/PqZi6ALA2LvmdIW1pRcvRcPKc=
4.簽名的完整請求參數(shù)
{
"sign":"mvzq0+Hn6bBkKAWaH3hJ4hDsQK/KxzxfzQVMDGigdfif+TERSnKvkoPiQtGikE2GjaVUoR8Td73mT4RSbk9H0PS0fPk9Twf//R4K/TKA8zlWq+QfI6OyKfoQmZmBNc1jEHqI21EzcP8EHsCgz/PqZi6ALA2LvmdIW1pRcvRcPKc=",
"body":{
"applicantInfo":{
"applicantName":"工保科技2",
"contactName":"劉備",
"contactPhone":"18325648012",
"identifyNumber":"91330100MA2KH64H27",
"insuredAddress":"網(wǎng)商路18號",
"managerId":"340824199001012223",
"managerName":"劉備"
},
"fileInfoList":{
"file":[
{
"fileName":"iEfp.jpg",
"filePath":"http://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg"
}
]
},
"insuredInfo":{
"contactName":"關(guān)羽",
"contactPhone":"18889901122",
"identifyAdd":"19號",
"identifyNumber":"340822199100902211",
"insuredName":"阿里巴巴"
},
"policyInfo":{
"endDate":"2021-11-13 23:59:59",
"insuranceDays":"120",
"orderNo":"1406911003941158914",
"areaCode":"330100000000",
"rate":"0.25",
"riskCode":"TB",
"specialClause":"1",
"startDate":"2021-07-17 00:00:00",
"sumInsured":"10000.0",
"sumPremium":"1.0"
},
"projectInfo":{
"engineeringAddress":"網(wǎng)商路18號",
"engineeringContractNum":"gyx199001",
"engineeringCost":"10000",
"planProjectEndDate":"2021-07-31 16:13:35",
"planProjectStartDate":"2021-07-29 16:13:35",
"projectCode":"AYC1000",
"projectTime":"120",
"projectType":"01",
"tenderName":"關(guān)羽",
"tenderProjectName":"希望工程"
}
}
}
5.測試
import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
public class RsaTest {
@Test
public void signTest() {
// 模擬公私鑰
final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDG05A2o1/7t3RGX1et5JZYd9cdQMV7YuE0f/F7wO9b0O1/RpfMWOkwurmgGoP8YpOMo6n15CTfyS5t2KW4wYCpUYrYSc548jRJWK+/c+8bkPXS4gZeRKoI2spag+Ntkh8wgqk59rX6rXf9GqDCjaDI6a+kFlocGyjub2Nwa09vVQIDAQAB";
final String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMbTkDajX/u3dEZfV63kllh31x1AxXti4TR/8XvA71vQ7X9Gl8xY6TC6uaAag/xik4yjqfXkJN/JLm3YpbjBgKlRithJznjyNElYr79z7xuQ9dLiBl5EqgjaylqD422SHzCCqTn2tfqtd/0aoMKNoMjpr6QWWhwbKO5vY3BrT29VAgMBAAECgYEAm33W+bP5G41URLjJhDgRkCxgsgL2rlEdGIa6nvK6/o49Pl1B19DsxWwyQVCbSeT5yXIxOBjs8YqPYd6ddAj4iZDCr0l0rQ1cM9qs44p4yC6Eba3ZHdUro86z8FltE3GxpcnL8H+WCAElBkGQ3Fer9O03Lcj3LgtEeYdUEswOdl0CQQDu8VzlWhHBhjDd0gFlskeoGmNpTHMXKJYa1AcdP+MuOsZQOQ1NU1ZXFw2wox/UPbEkHzbT/9M0Tvz0gpa0+vZvAkEA1QUUlAArvdPiq9d+J6tvXLgca9IUiyLpRTxLCGOnAbkxMZ2KgHwpnlqf7vAD5BOOpJATwEmY1O/yT2Psk7l4ewJBAKqzvE4N/slm+NpAAceJii/KSmMbvs04raQU/dAjqEWKr8r4N0ya0P/+9ETRBRg3yqmnsx/ZkCW6mHSGJuy8rfkCQH35jCreUv/m32TqgoOpQalehAhLa7TAx50XQ/RJIonFYE9MMI09YEtyoqRmMpbd7fxp7BRKMeSzpePHXzAZfiMCQAQG0GDh9Rqwz+QJHLU1c3jYQeb9IF6sOqVu6TmNXiJTf0XUa/EBuljNF9Zn2nb1Yz/xUC/PX8NFlKI+jVJITUc=";
// 保司生成自己的私鑰和公鑰
//final RSA rsa = new RSA(AsymmetricAlgorithm.RSA_ECB.getValue());
//String publicKey = rsa.getPublicKeyBase64();
//String privateKey = rsa.getPrivateKeyBase64();
log.info("publicKey:{}", publicKey);
log.info("privateKey:{}", privateKey);
// 待簽數(shù)據(jù)
String data = "{\n" +
" \"body\":{\n" +
" \"applicantInfo\":{\n" +
" \"applicantName\":\"工钡芫ⅲ科技2\",\n" +
" \"contactName\":\"劉備\",\n" +
" \"contactPhone\":\"18325648012\",\n" +
" \"identifyNumber\":\"91330100MA2KH64H27\",\n" +
" \"insuredAddress\":\"網(wǎng)商路18號\",\n" +
" \"managerId\":\"340824199001012223\",\n" +
" \"managerName\":\"劉備\"\n" +
" },\n" +
" \"fileInfoList\":{\n" +
" \"file\":[\n" +
" {\n" +
" \"fileName\":\"iEfp.jpg\",\n" +
" \"filePath\":\"http://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"insuredInfo\":{\n" +
" \"contactName\":\"關(guān)羽\",\n" +
" \"contactPhone\":\"18889901122\",\n" +
" \"identifyAdd\":\"19號\",\n" +
" \"identifyNumber\":\"340822199100902211\",\n" +
" \"insuredName\":\"阿里巴巴\"\n" +
" },\n" +
" \"policyInfo\":{\n" +
" \"endDate\":\"2021-11-13 23:59:59\",\n" +
" \"insuranceDays\":\"120\",\n" +
" \"orderNo\":\"1406911003941158914\",\n" +
" \"areaCode\":\"330100000000\",\n" +
" \"rate\":\"0.25\",\n" +
" \"riskCode\":\"TB\",\n" +
" \"specialClause\":\"1\",\n" +
" \"startDate\":\"2021-07-17 00:00:00\",\n" +
" \"sumInsured\":\"10000.0\",\n" +
" \"sumPremium\":\"1.0\"\n" +
" },\n" +
" \"projectInfo\":{\n" +
" \"engineeringAddress\":\"網(wǎng)商路18號\",\n" +
" \"engineeringContractNum\":\"gyx199001\",\n" +
" \"engineeringCost\":\"10000\",\n" +
" \"planProjectEndDate\":\"2021-07-31 16:13:35\",\n" +
" \"planProjectStartDate\":\"2021-07-29 16:13:35\",\n" +
" \"projectCode\":\"AYC1000\",\n" +
" \"projectTime\":\"120\",\n" +
" \"projectType\":\"01\",\n" +
" \"tenderName\":\"關(guān)羽\",\n" +
" \"tenderProjectName\":\"希望工程\"\n" +
" }\n" +
" }\n" +
"}";
log.info("待簽數(shù)據(jù):{}", data);
final String sign = RsaUtils.generateSign(data, privateKey);
log.info("簽名:{}", sign);
final boolean verify = RsaUtils.verifySign(data, sign, publicKey);
log.info("驗(yàn)簽結(jié)果:{}", verify);
}
}
Maven 依賴
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.3</version>
</dependency>
簽名工具參考代碼
package com.gb.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Created with IntelliJ IDEA.
*
* @author sunkailun
* @DateTime 2018/5/9 下午1:53
* @email 376253703@qq.com
* @phone 13777579028
* @explain
*/
public class RsaUtils {
/**
* 生成簽名
*
* @param json: 參數(shù)
* @return java.lang.String
* @author sunkailun
* @DateTime 2018/5/9 下午2:01
* @email 376253703@qq.com
* @phone 13777579028
*/
public static String generateSign(String json, String privateKey) {
//簽名規(guī)則
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKey, null);
//參數(shù)值
StringBuffer param = new StringBuffer();
//循環(huán)拼接參數(shù)
mapToString(JsonUtil.bean(json, TreeMap.class), param);
System.out.println("簽名拼接: " + param.toString());
//將String轉(zhuǎn)換為byte
byte[] data = Convert.toStr(param).getBytes();
//簽名
byte[] signed = sign.sign(data);
return Base64.encode(signed);
}
/**
* 簽名驗(yàn)證
*
* @param json: 參數(shù)
* @param signData: 簽名
* @return boolean
* @author sunkailun
* @DateTime 2018/5/9 下午1:59
* @email 376253703@qq.com
* @phone 13777579028
*/
public static boolean verifySign(String json, String signData, String publicKey) {
//簽名規(guī)則
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, publicKey);
//參數(shù)值
StringBuffer param = new StringBuffer();
//循環(huán)拼接參數(shù)
mapToString(JsonUtil.bean(json, TreeMap.class), param);
//將String轉(zhuǎn)換為byte
byte[] data = Convert.toStr(param).getBytes();
//驗(yàn)證簽名
Boolean verify = sign.verify(data, Base64.decode(signData));
//返回
return verify;
}
/**
* map轉(zhuǎn)字符串
*
* @param map 集合
* @param param 追加參數(shù)
* @return
*/
public static void mapToString(TreeMap<String, Object> map, StringBuffer param) {
//循環(huán)集合
for (String key : map.keySet()) {
//值
Object obj = map.get(key);
//判斷不同類型祷安,執(zhí)行不同參數(shù)轉(zhuǎn)換
if (obj instanceof List) {
// 轉(zhuǎn)list
List<TreeMap<String, Object>> list = Convert.convert(List.class, obj);
// 遞歸遍歷
for (Object m : list) {
mapToString(Convert.convert(TreeMap.class, m), param);
}
} else if (obj instanceof Map) {
// 遞歸遍歷
mapToString(Convert.convert(TreeMap.class, obj), param);
} else {
//判斷是否為空
if (org.apache.commons.lang3.StringUtils.isNotBlank(Convert.toStr(obj))) {
//附值
param.append(Convert.toStr(obj));
}
}
}
}
}
package com.gb.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.commons.lang3.StringUtils;
import java.text.SimpleDateFormat;
import java.util.List;
/**
* json轉(zhuǎn)換工具類
*
* @author sunkailun
* @DateTime 2020/12/27 下午4:42
* @email 376253703@qq.com
* @phone 13777579028
*/
public class JsonUtil {
private static ObjectMapper mapper = new ObjectMapper();
/**
* 對靜態(tài)變量進(jìn)行統(tǒng)一參數(shù)設(shè)置
*/
static {
//序列化日期時(shí)是否以timestamps輸出
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//設(shè)置可用單引號
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//設(shè)置實(shí)體無屬性和json串屬性對應(yīng)時(shí)不會出錯(cuò)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//對象為null不進(jìn)行序列化
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//設(shè)置時(shí)間格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
/**
* @return ObjectMapper 返回類型
* @throws
* @Title: getJsonMapper 方法名
* @Description: TODO 執(zhí)行內(nèi)容:返回ObjectMapper
*/
public static ObjectMapper getJsonMapper() {
return mapper;
}
/**
* @param value
* @param basicClass
* @return T 返回類型
* @throws
* @Title: java 方法名
* @Description: 執(zhí)行內(nèi)容:將json轉(zhuǎn)換為對象
*/
public static <T> T bean(String value, Class<T> basicClass) {
//判斷參數(shù)為空直徑返回null
if (StringUtils.isEmpty(value)) {
return null;
}
try {
//將json轉(zhuǎn)換為java類
return mapper.readValue(value, basicClass);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/**
* @param value
* @param classType
* @return T 返回類型
* @throws
* @Title: java 方法名
* @Description: 執(zhí)行內(nèi)容:將json轉(zhuǎn)換為對象
*/
public static <T> List<T> list(String value, Class<T> classType) {
//判斷參數(shù)為空直徑返回null
if (StringUtils.isEmpty(value)) {
return null;
}
try {
JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, classType);
//將json轉(zhuǎn)換為java類
return mapper.readValue(value, javaType);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/**
* @param value
* @return String 返回類型
* @throws
* @Title: json 方法名
* @Description: 執(zhí)行內(nèi)容:將對象轉(zhuǎn)為json
*/
public static String json(Object value) {
try {
//將java類轉(zhuǎn)換為json
return mapper.writeValueAsString(value);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
驗(yàn)證
加簽邏輯驗(yàn)證
開發(fā)者實(shí)現(xiàn)加簽邏輯之后,使用計(jì)算示例中步驟 1 的初始請求參數(shù)作為輸入兔乞,結(jié)合密鑰生成中的示例私鑰,進(jìn)行 RSA 簽名的生成凉唐,如果結(jié)果與步驟 3 中最終簽名串一致庸追,說明加簽邏輯正確。
驗(yàn)簽邏輯驗(yàn)證
使用計(jì)算示例中步驟 4 完整請求參數(shù)作為輸入台囱,結(jié)合密鑰生成中的示例公鑰淡溯,進(jìn)行 RSA 簽名的 check ,返回 true 則說明驗(yàn)簽邏輯正確簿训。