签名与验签

技术术语

概念和使用(如图)

image.png
  • 工保网公钥是平台生成的,平台自己保存私钥,开发者保存公钥,主要作用是:让开发者验证工保网申请接口相关数据

  • 开发者公钥是开发者生成的,开发者自己保存私钥,公钥提交到工保网,主要作用是:让工保网校验来自开发者的回调数据

限定

基本限定

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 依赖

        <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: 参数
     * @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) {
        //签名规则
        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 376253703@qq.com
     * @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<String, Object> map, StringBuffer param) {
        //循环集合
        for (String key : map.keySet()) {
            //值
            Object obj = map.get(key);
            //判断不同类型,执行不同参数转换
            if (obj instanceof List) {
                // 转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转换工具类
 *
 * @author sunkailun
 * @DateTime 2020/12/27  下午4:42
 * @email 376253703@qq.com
 * @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> T bean(String value, Class<T> 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 <T> List<T> list(String value, Class<T> 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 则说明验签逻辑正确。

附:

在线公私钥生成工具

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容