codecamp

百度智能小程序 签名与验签

本章节主要介绍百度收银台使用的双向RSA加密签名规则与相关示例。

基本限定

1.统一字符集:UTF-8 ;
百度收银台接口中的所有参数字符集均保持为 UTF-8 ,与收银台交互接入或者计算签名时,要统一使用 UTF-8 ,暂不支持其他字符集,请接入业务方自行转换;
2.请求接口的参数列表均为字符串类型键值对;
3.键值中如果为复杂数据类型,比如结构体、数组、对象都必须先转化为 JSON 结构字符串;
4.参数中包含汉字的部分,需要做 URLEncode 处理。

签名规则

1.排除参数列表中名为 sign 和 sign_type 的参数;
2.将剩余参数按参数名字典序正序排列;
3.将参数与其对应的值使用 “=” 连接,组成参数字符串,将参数字符串按排序结果,使用 “&” 连接,组成待签名字符串;
4.将待签名字符串和业务方私钥使用 SHA1WithRSA 签名算法得出最终签名。

签名计算过程示例

使用密钥生成中的示例公私钥来做计算演示。

1.初始请求业务参数

参数名 示例取值
appKey MMMabc
dealId 470193086
tpOrderId 3028903626
totalAmount 11300

2.生成待签名字符串

appKey=MMMabc&dealId=470193086&totalAmount=11300&tpOrderId=3028903626

3.生成最终签名串

TN0ZNPyQeTnPjCN5hUa7JwrXOhR8uDASXPazidVQHFSiGCH5aouBkVvJxtf8PeqzGYWAASwS2oOt2eJfunzC5dTFd/pWJeJToMgCSgRY7KtQUCCDnMrtpqiMAf+dLiXps3HpWhVB4CK6MXfHc64ejP5a2fu5bg8B0BTcHrqaGc0=

4.签名的完整请求参数

参数名 示例取值
appKey MMMabc
dealId 470193086
tpOrderId 3028903626
totalAmount 11300
rsaSign TN0ZNPyQeTnPjCN5hUa7JwrXOhR8uDASXPazidVQHFSiGCH5aouBkVvJxtf8PeqzGYWAASwS2oOt2eJfunzC5dTFd/pWJeJToMgCSgRY7KtQUCCDnMrtpqiMAf+dLiXps3HpWhVB4CK6MXfHc64ejP5a2fu5bg8B0BTcHrqaGc0=

签名工具参考代码

  • PHP签名工具类
<?php

/**
 * 通用签名工具
 *
 * 基于openssl扩展,提供使用私钥生成签名和使用公钥验证签名的接口
 *
 **/
class RSASign
{

    /**
     * @desc 使用私钥生成签名字符串
     * @param array $assocArr 入参数组
     * @param string $rsaPriKeyStr 私钥原始字符串,不含PEM格式前后缀
     * @return string 签名结果字符串
     * @throws Exception
     */
    public static function sign(array $assocArr, $rsaPriKeyStr)
    {
        $sign = '';
        if (empty($rsaPriKeyStr) || empty($assocArr)) {
            return $sign;
        }

        if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) {
            throw new Exception("openssl扩展不存在");
        }

        $rsaPriKeyPem = self::convertRSAKeyStr2Pem($rsaPriKeyStr, 1);

        $priKey = openssl_pkey_get_private($rsaPriKeyPem);

        if (isset($assocArr['sign'])) {
            unset($assocArr['sign']);
        }

        ksort($assocArr); // 参数按字典顺序排序

        $parts = array();
        foreach ($assocArr as $k => $v) {
            $parts[] = $k . '=' . $v;
        }
        $str = implode('&', $parts);

        openssl_sign($str, $sign, $priKey);
        openssl_free_key($priKey);

        return base64_encode($sign);
    }

    /**
     * @desc 使用公钥校验签名
     * @param array $assocArr 入参数据,签名属性名固定为rsaSign
     * @param string $rsaPubKeyStr 公钥原始字符串,不含PEM格式前后缀
     * @return bool true 验签通过|false 验签不通过
     * @throws Exception
     */
    public static function checkSign(array $assocArr, $rsaPubKeyStr)
    {
        if (!isset($assocArr['rsaSign']) || empty($assocArr) || empty($rsaPubKeyStr)) {
            return false;
        }

        if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
            throw new Exception("openssl扩展不存在");
        }

        $sign = $assocArr['rsaSign'];
        unset($assocArr['rsaSign']);

        if (empty($assocArr)) {
            return false;
        }

        ksort($assocArr); // 参数按字典顺序排序

        $parts = array();
        foreach ($assocArr as $k => $v) {
            $parts[] = $k . '=' . $v;
        }
        $str = implode('&', $parts);

        $sign = base64_decode($sign);

        $rsaPubKeyPem = self::convertRSAKeyStr2Pem($rsaPubKeyStr);
        $pubKey = openssl_pkey_get_public($rsaPubKeyPem);

        $result = (bool)openssl_verify($str, $sign, $pubKey);
        openssl_free_key($pubKey);

        return $result;
    }


    /**
     * @desc 将密钥由字符串(不换行)转为PEM格式
     * @param string $rsaKeyStr 原始密钥字符串
     * @param int $keyType 0 公钥|1 私钥,默认0
     * @return string PEM格式密钥
     * @throws Exception
     */
    public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0)
    {

        $pemWidth = 64;
        $rsaKeyPem = '';

        $begin = '-----BEGIN ';
        $end = '-----END ';
        $key = ' KEY-----';
        $type = $keyType ? 'PRIVATE' : 'PUBLIC';

        $keyPrefix = $begin . $type . $key;
        $keySuffix = $end . $type . $key;

        $rsaKeyPem .= $keyPrefix . "\n";
        $rsaKeyPem .= wordwrap($rsaKeyStr, $pemWidth, "\n", true) . "\n";
        $rsaKeyPem .= $keySuffix;

        if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_pkey_get_private')) {
            return false;
        }

        if ($keyType == 0 && false == openssl_pkey_get_public($rsaKeyPem)) {
            return false;
        }

        if ($keyType == 1 && false == openssl_pkey_get_private($rsaKeyPem)) {
            return false;
        }

        return $rsaKeyPem;
    }

}

  • Java签名工具类
/*
 * Copyright (C) 2020 Baidu, Inc. All Rights Reserved.
 */
package com.baidu.*;

import static org.springframework.util.Assert.isTrue;
import static org.springframework.util.Assert.notNull;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
 * 百度收银台双向RSA签名工具
 * JDK版本要求:1.8+
 */
public class RSASign {

    private static final String CHARSET = "UTF-8";
    private static final String SIGN_TYPE_RSA = "RSA";
    private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
    private static final String SIGN_KEY = "rsaSign";

    /**
     * 使用私钥生成签名字符串
     *
     * @param params     待签名参数集合
     * @param privateKey 私钥原始字符串
     *
     * @return 签名结果字符串
     *
     * @throws Exception
     */
    public static String sign(Map<String, Object> params, String privateKey) throws Exception {
        isTrue(!CollectionUtils.isEmpty(params), "params is required");
        notNull(privateKey, "privateKey is required");

        String signContent = signContent(params);

        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
        signature.initSign(getPrivateKeyPKCS8(privateKey));
        signature.update(signContent.getBytes(CHARSET));
        byte[] signed = signature.sign();

        return new String(Base64.getEncoder().encode(signed));
    }

    /**
     * 使用公钥校验签名
     *
     * @param params    入参数据,签名属性名固定为rsaSign
     * @param publicKey 公钥原始字符串
     *
     * @return true 验签通过 | false 验签不通过
     *
     * @throws Exception
     */
    public static boolean checkSign(Map<String, Object> params, String publicKey) throws Exception {
        isTrue(!CollectionUtils.isEmpty(params), "params is required");
        notNull(publicKey, "publicKey is required");

        // sign & content
        String content = signContent(params);
        String rsaSign = params.get(SIGN_KEY).toString();

        // verify
        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
        signature.initVerify(getPublicKeyX509(publicKey));
        signature.update(content.getBytes(CHARSET));

        return signature.verify(Base64.getDecoder().decode(rsaSign.getBytes(CHARSET)));
    }

    /**
     * 对输入参数进行key过滤排序和字符串拼接
     *
     * @param params 待签名参数集合
     *
     * @return 待签名内容
     *
     * @throws UnsupportedEncodingException
     */
    private static String signContent(Map<String, Object> params) throws UnsupportedEncodingException {
        Map<String, String> sortedParams = new TreeMap<>(Comparator.naturalOrder());
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            if (legalKey(key)) {
                String value =
                        entry.getValue() == null ? null : URLEncoder.encode(entry.getValue().toString(), CHARSET);
                sortedParams.put(key, value);
            }
        }

        StringBuilder builder = new StringBuilder();
        if (!CollectionUtils.isEmpty(sortedParams)) {
            for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
                builder.append(entry.getKey());
                builder.append("=");
                builder.append(entry.getValue());
                builder.append("&");
            }
            builder.deleteCharAt(builder.length() - 1);
        }
        return builder.toString();
    }

    /**
     * 将公钥字符串进行Base64 decode之后,生成X509标准公钥
     *
     * @param publicKey 公钥原始字符串
     *
     * @return X509标准公钥
     *
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException,
            NoSuchAlgorithmException, UnsupportedEncodingException {
        if (StringUtils.isEmpty(publicKey)) {
            return null;
        }
        KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
        byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(CHARSET));
        return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
    }

    /**
     * 将私钥字符串进行Base64 decode之后,生成PKCS #8标准的私钥
     *
     * @param privateKey 私钥原始字符串
     *
     * @return PKCS #8标准的私钥
     *
     * @throws Exception
     */
    private static PrivateKey getPrivateKeyPKCS8(String privateKey) throws Exception {
        if (StringUtils.isEmpty(privateKey)) {
            return null;
        }
        KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
        byte[] decodedKey = Base64.getDecoder().decode(privateKey.getBytes(CHARSET));
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
    }

    /**
     * 有效的待签名参数key值
     * 非空、且非签名字段
     *
     * @param key 待签名参数key值
     *
     * @return true | false
     */
    private static boolean legalKey(String key) {
        return StringUtils.hasText(key) && !SIGN_KEY.equalsIgnoreCase(key);
    }

}

验证

加签逻辑验证

开发者实现加签逻辑之后,使用计算示例中步骤 1 的初始请求参数作为输入,结合密钥生成中的示例私钥,进行 RSA 签名的生成,如果结果与步骤 3 中最终签名串一致,说明加签逻辑正确。

验签逻辑验证

使用计算示例中步骤 4 完整请求参数作为输入,结合密钥生成中的示例公钥,进行 RSA 签名的 check ,返回 true 则说明验签逻辑正确。

相关链接


百度智能小程序 核心参数获取与组装
百度智能小程序 密钥生成
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

百度智能小程序开发文档

百度智能小程序 组件

百度智能小程序 地图

百度智能小程序 画布

百度智能小程序 API

百度智能小程序 界面

百度智能小程序 关注小程序引导组件

百度智能小程序 自定义组件

百度智能小程序 媒体

百度智能小程序 设备

百度智能小程序 拨打电话

百度智能小程序 内存警报

百度智能小程序 手机联系人

百度智能小程序 用户截屏事件

百度智能小程序 第三方平台

百度智能小程序 开放接口

百度智能小程序 百度收银支付

百度智能小程序 分包预下载

百度智能小程序 数据分析

百度智能小程序 服务端

百度智能小程序 云开发

百度智能小程序 初始化

百度智能小程序 云函数

百度智能小程序 服务端初始化

百度智能小程序 服务器获取上下文

百度智能小程序 服务端云函数

百度智能小程序 开发教程

百度智能小程序 功能开发

百度智能小程序 基本原理

百度智能小程序 小程序自动化

百度智能小程序 视频教程

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }