平台对接的数据签名加解密

标题平台对接的数据签名加解密

标题数据签名介绍

接口平台调用第三方接口时会使用私钥对数据签名,第三方务必使用公钥验签,保证接口安全性。
签名规则与流程:
1.筛选,获取所有请求参数后剔除空值参数;
2.排序,将筛选的参数按第一个字符的键ASCII码升序排序,若第一个字符相同则取后一个,以此类推;
3.拼接,将排序后的参数拼接为“参数键=参数值”的格式,并且把这些参数用“&”字符连接起来,此时生成的字符串为待签名字符串;
4.签名,通过私钥对上述待签名字符串进行签名,签名算法为RSA2,请求接口时签名参数为sign;
5.验签,取出所有请求参数后剔除公共参数sign,然后按上述1、2和3的规则处理,通过RSA2算法使用公钥验签。
注意:验签时一定注意剔除公共参数sign,一定注意取出所有的请求参数,防止后续接口增加参数时导致验签失败。

标题加密解密样例代码

生成工具类

import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 测试签名与验签
 *
 * @author 可乐加糖
 * @since 2024/6/27
 */
@Slf4j
public class TestSha256WithRsa {

    /**
     * 测试请求参数的签名与验签
     * <p>
     * 依赖工具包:<a href="https://github.com/dromara/hutool">hutool</a>
     */
    @Test
    public void testSignAndVerifyWithHutool() {
        // 创建公私钥对
        RSA rsa = SecureUtil.rsa();
        String privateKeyBase64 = rsa.getPrivateKeyBase64();
        log.info("私钥(接口平台不对外暴露):{}", privateKeyBase64);
        String publicKeyBase64 = rsa.getPublicKeyBase64();
        log.info("公钥(暴露给第三方开发者):{}", publicKeyBase64);
        Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKeyBase64, publicKeyBase64);

        // 示例请求参数值
        Map<String, Object> params = MapUtil.<String, Object>builder()
                .put("name", "张三")
                .put("age", 18)
                .put("gender", "male")
                .put("mobile", "15512345678")
                .put("address", "")
                .put("wechat", null)
                .build();
        log.info("待签名的参数:{}", params);

        // 将参数拼接为字符串
        String paramStr = this.buildParamStr(params);
        log.info("经过筛选、排序、拼接后的待签名字符串:{}", paramStr);

        // 私钥签名,算法为RSA2,即SHA256withRSA
        byte[] signBytes = sign.sign(paramStr, StandardCharsets.UTF_8);
        String signStr = Base64.encode(signBytes);
        log.info("数据签名:{}", signStr);

        // 平台方调用第三方接口时,添加公共字段,即签名字段sign
        params.put("sign", signStr);

        // 第三方验签时,取出签名字段,取出请求参数并拼接为字符串,然后使用公钥验签
        String requestSign = (String) params.get("sign");
        String requestParams = this.buildParamStr(params);
        boolean verify = sign.verify(requestParams.getBytes(StandardCharsets.UTF_8), Base64.decode(requestSign));
        log.info("验签结果:{}", verify);
    }

    /**
     * 将参数转换为待签名的字符串
     *
     * @param params 待签名参数
     * @return 待签名字符串
     */
    private String buildParamStr(Map<String, Object> params) {
        Assert.notEmpty(params, "待签名参数不能为空");
        return params.entrySet()
                .stream()
                // 1.筛选,剔除公共参数和空值参数
                .filter(entry -> {
                    // 剔除公共参数sign
                    if (StrUtil.equals(entry.getKey(), "sign")) {
                        return false;
                    }

                    // 所有待签名的参数的key和value都不能为null或空串
                    return ObjectUtil.isAllNotEmpty(entry.getKey(), entry.getValue());
                })
                // 2.排序,将筛选的参数按第一个字符的键ASCII码升序排序,若第一个字符相同则取后一个,以此类推
                .sorted((o1, o2) -> {
                    String key1 = o1.getKey();
                    String key2 = o2.getKey();
                    return key1.compareTo(key2);
                })
                // 3.拼接,key和value间使用“=”连接,即“key=value”,每组参数间使用“&”连接,即“key1=value1&key2=value2”
                .map(entry -> entry.getKey() + "=" + entry.getValue().toString())
                .collect(Collectors.joining("&"));
    }
}

校验签名方法

 // 方式1 verify1(data.getBytes(StandardCharsets.UTF_8), getPublicKey(), sign);
 public static boolean verify1(byte[] data, String publicKey, String sign) throws Exception {
        byte[] keyBytes = RSAUtils.decryptBASE64(publicKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(pubKey);
        signature.update(data);
        return signature.verify(RSAUtils.decryptBASE64(sign));
    }

	// 方式2 verify2(params, getPublicKey(), signStr)
    public boolean verify2(Map<String, Object> params,String publicKey,String signStr){
        Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, publicKey);
        String requestParams = this.buildParamStr(params);
        boolean verify = sign.verify(requestParams.getBytes(StandardCharsets.UTF_8), Base64.decode(signStr));
        return verify;
    }

Postman测试

如下图所示
在这里插入图片描述
请求体:

{
	"deviceid": "113922989",
	"startTime": "2024-08-24 12:00:00",
	"endTime": "2024-08-25 12:10:00",
	"sign": "gFxLUwBlfHjXsiMh6dAJk52dl3LW74NcTC8L5kHAy7es7KIw8Cbj2Sy2VJK9vDwkDU2vKx1J1Y0bTIWqIEgIyvy4DufBfSkyYLOHXfMryNS0dwYHy+O94nlD0vYkML/XqGCcBt99KWWiw8OgfSSRUW0869Kz0c5TzvRxSjodrbc4uRaF/p/Obl9geXEE8rBxlPiFYjVHpZA7ZjkAyMdjXti3atghMFfYu5lkS4AzuyNQ9eijC4XqG3rgpZPrLQAYF4rxBJTkR23seGX5iuNLX9yll1PldlUR6KHG8Z/SDAVQgce/Zm3N198k/YId4odJJVDGZN69eUABULSWSXcFQQ=="
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值