簽名與驗(yàn)簽

技術(shù)術(shù)語

概念和使用(如圖)

image.png
  • 工保網(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ù)可能是全量字段(動(dòng)態(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":"工??萍?",
            "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.生成待簽名字符串

工??萍?劉備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)簽邏輯正確。

附:

在線公私鑰生成工具

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容