技术术语
概念和使用(如图)
工保网公钥是平台生成的,平台自己保存私钥,开发者保存公钥,主要作用是:让开发者验证工保网申请接口相关数据
开发者公钥是开发者生成的,开发者自己保存私钥,公钥提交到工保网,主要作用是:让工保网校验来自开发者的回调数据
限定
基本限定
1.统一字符集:UTF-8 ; 2.请求接口的参数列表均为字符串类型键值对;
签名规则
1.排除参数列表中名为 sign的参数; 2.将剩余参数按参数名字典序正序排列; 3.将参数与其对应的值使用 “” 连接,组成参数字符串; 4.将待签名字符串和业务方私钥使用 SHA256withRSA 签名算法得出最终签名。
支持的签名算法
名称 | 标准签名算法名称 | 描述 |
---|---|---|
RSA2 | SHA256WithRSA | 强制要求 RSA 密钥的长度至少为 2048 |
注意:参与加签数据可能是全量字段(动态字段)建议验签使用JSON或者Map接受数据进行验签
验签加签Demo点击下载
签名计算过程示例
使用密钥生成中的示例公私钥来做计算演示。
1.初始请求业务参数
{
"body":{
"applicantInfo":{
"applicantName":"工保科技2",
"contactName":"刘备",
"contactPhone":"18325648012",
"identifyNumber":"91330100MA2KH64H27",
"insuredAddress":"网商路18号",
"managerId":"340824199001012223",
"managerName":"刘备"
},
"fileInfoList":{
"file":[
{
"fileName":"iEfp.jpg",
"filePath":"http://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg"
}
]
},
"insuredInfo":{
"contactName":"关羽",
"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":"网商路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":"关羽",
"tenderProjectName":"希望工程"
}
}
}
2.生成待签名字符串
工保科技2刘备1832564801291330100MA2KH64H27网商路18号340824199001012223刘备iEfp.jpghttp://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg关羽1888990112219号340822199100902211阿里巴巴3301000000002021-11-13 23:59:5912014069110039411589140.25TB12021-07-17 00:00:0010000.01.0网商路18号gyx199001100002021-07-31 16:13:352021-07-29 16:13:35AYC100012001关羽希望工程
3.生成最终签名串
mvzq0+Hn6bBkKAWaH3hJ4hDsQK/KxzxfzQVMDGigdfif+TERSnKvkoPiQtGikE2GjaVUoR8Td73mT4RSbk9H0PS0fPk9Twf//R4K/TKA8zlWq+QfI6OyKfoQmZmBNc1jEHqI21EzcP8EHsCgz/PqZi6ALA2LvmdIW1pRcvRcPKc=
4.签名的完整请求参数
{
"sign":"mvzq0+Hn6bBkKAWaH3hJ4hDsQK/KxzxfzQVMDGigdfif+TERSnKvkoPiQtGikE2GjaVUoR8Td73mT4RSbk9H0PS0fPk9Twf//R4K/TKA8zlWq+QfI6OyKfoQmZmBNc1jEHqI21EzcP8EHsCgz/PqZi6ALA2LvmdIW1pRcvRcPKc=",
"body":{
"applicantInfo":{
"applicantName":"工保科技2",
"contactName":"刘备",
"contactPhone":"18325648012",
"identifyNumber":"91330100MA2KH64H27",
"insuredAddress":"网商路18号",
"managerId":"340824199001012223",
"managerName":"刘备"
},
"fileInfoList":{
"file":[
{
"fileName":"iEfp.jpg",
"filePath":"http://gongbaojinji.oss-cn-hangzhou.aliyuncs.com/insurance/TJ3wQriEfp.jpg"
}
]
},
"insuredInfo":{
"contactName":"关羽",
"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":"网商路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":"关羽",
"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);
// 待签数据
String data = "{\n" +
" \"body\":{\n" +
" \"applicantInfo\":{\n" +
" \"applicantName\":\"工保科技2\",\n" +
" \"contactName\":\"刘备\",\n" +
" \"contactPhone\":\"18325648012\",\n" +
" \"identifyNumber\":\"91330100MA2KH64H27\",\n" +
" \"insuredAddress\":\"网商路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\":\"关羽\",\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\":\"网商路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\":\"关羽\",\n" +
" \"tenderProjectName\":\"希望工程\"\n" +
" }\n" +
" }\n" +
"}";
log.info("待签数据:{}", data);
final String sign = RsaUtils.generateSign(data, privateKey);
log.info("签名:{}", sign);
final boolean verify = RsaUtils.verifySign(data, sign, publicKey);
log.info("验签结果:{}", verify);
}
}
Maven 依赖
cn.hutool
hutool-all
5.6.3
签名工具参考代码
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 [email protected]
* @phone 13777579028
* @explain
*/
public class RsaUtils {
/**
* 生成签名
*
* @param json: 参数
* @return java.lang.String
* @author sunkailun
* @DateTime 2018/5/9 下午2:01
* @email [email protected]
* @phone 13777579028
*/
public static String generateSign(String json, String privateKey) {
//签名规则
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKey, null);
//参数值
StringBuffer param = new StringBuffer();
//循环拼接参数
mapToString(JsonUtil.bean(json, TreeMap.class), param);
System.out.println("签名拼接: " + param.toString());
//将String转换为byte
byte[] data = Convert.toStr(param).getBytes();
//签名
byte[] signed = sign.sign(data);
return Base64.encode(signed);
}
/**
* 签名验证
*
* @param json: 参数
* @param signData: 签名
* @return boolean
* @author sunkailun
* @DateTime 2018/5/9 下午1:59
* @email [email protected]
* @phone 13777579028
*/
public static boolean verifySign(String json, String signData, String publicKey) {
//签名规则
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, publicKey);
//参数值
StringBuffer param = new StringBuffer();
//循环拼接参数
mapToString(JsonUtil.bean(json, TreeMap.class), param);
//将String转换为byte
byte[] data = Convert.toStr(param).getBytes();
//验证签名
Boolean verify = sign.verify(data, Base64.decode(signData));
//返回
return verify;
}
/**
* map转字符串
*
* @param map 集合
* @param param 追加参数
* @return
*/
public static void mapToString(TreeMap map, StringBuffer param) {
//循环集合
for (String key : map.keySet()) {
//值
Object obj = map.get(key);
//判断不同类型,执行不同参数转换
if (obj instanceof List) {
// 转list
List> 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转换工具类
*
* @author sunkailun
* @DateTime 2020/12/27 下午4:42
* @email [email protected]
* @phone 13777579028
*/
public class JsonUtil {
private static ObjectMapper mapper = new ObjectMapper();
/**
* 对静态变量进行统一参数设置
*/
static {
//序列化日期时是否以timestamps输出
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置可用单引号
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//设置实体无属性和json串属性对应时不会出错
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//对象为null不进行序列化
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//设置时间格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
/**
* @return ObjectMapper 返回类型
* @throws
* @Title: getJsonMapper 方法名
* @Description: TODO 执行内容:返回ObjectMapper
*/
public static ObjectMapper getJsonMapper() {
return mapper;
}
/**
* @param value
* @param basicClass
* @return T 返回类型
* @throws
* @Title: java 方法名
* @Description: 执行内容:将json转换为对象
*/
public static T bean(String value, Class basicClass) {
//判断参数为空直径返回null
if (StringUtils.isEmpty(value)) {
return null;
}
try {
//将json转换为java类
return mapper.readValue(value, basicClass);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/**
* @param value
* @param classType
* @return T 返回类型
* @throws
* @Title: java 方法名
* @Description: 执行内容:将json转换为对象
*/
public static List list(String value, Class classType) {
//判断参数为空直径返回null
if (StringUtils.isEmpty(value)) {
return null;
}
try {
JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, classType);
//将json转换为java类
return mapper.readValue(value, javaType);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
/**
* @param value
* @return String 返回类型
* @throws
* @Title: json 方法名
* @Description: 执行内容:将对象转为json
*/
public static String json(Object value) {
try {
//将java类转换为json
return mapper.writeValueAsString(value);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
验证
加签逻辑验证
开发者实现加签逻辑之后,使用计算示例中步骤 1 的初始请求参数作为输入,结合密钥生成中的示例私钥,进行 RSA 签名的生成,如果结果与步骤 3 中最终签名串一致,说明加签逻辑正确。
验签逻辑验证
使用计算示例中步骤 4 完整请求参数作为输入,结合密钥生成中的示例公钥,进行 RSA 签名的 check ,返回 true 则说明验签逻辑正确。