Skip to content

签名与验签

签名算法

  • 签名算法为SHA256WithRSA,密钥长度为2048位(bit)。

签名过程

  • 首先,对接口请求参数中除了参数signature之外的所有非空数据元明文按照key的ascii顺序排序,并以&作为连接符拼接成待签名串(即key1=value1&key2=value2…);
  • 然后,使用设备终端的 RSA 私钥对待签名串进行 SHA256 with RSA 签名;
  • 最后,对签名做Base64编码,将编码后的签名串放在签名(signature)域里和其他报文域一起上送给系统。

注意:若请求参数中有需要加密的敏感信息,要先使用明文进行签名,再进行加密。

验签过程

  • 首先,对接口返回参数中除了参数signature之外的所有非空数据元明文按照key的ascii顺序排序,并以&作为连接符拼接成待签名串(即key1=value1&key2=value2…);
  • 然后,使用系统的 RSA 公钥对待签名串进行 SHA256 with RSA 签名验证。

注意:若返回报文中有加密的敏感信息存在,需先对敏感信息进行解密,得到敏感信息明文,再进行签名验证。

代码示例

  • 参数拼接

Java 实现

java
/**
     * 参数拼接
     * @param paramMap 原数据
     * @return 拼接后的字符串
     */
    public static String sortMapToString(Map<String, String> paramMap) {
        StringBuilder result = new StringBuilder();
        Collection<String> keySet = paramMap.keySet();
        List<String> list = new ArrayList<>(keySet);
        Collections.sort(list);
        for (String key : list) {
            if ("signature".equals(key)) {
                continue;
            }
            if (StringUtils.isNotBlank(paramMap.get(key))) {
                result.append(key).append("=").append(paramMap.get(key)).append("&");
            }
        }
        return result.substring(0, result.length() - 1);
    }

.NET 实现

.NET
    public static string SortMapToString(IDictionary<string, string> paramMap)
    {
        if (paramMap == null || paramMap.Count == 0)
            return string.Empty;

        // 过滤掉signature参数和空值
        var validParams = paramMap
            .Where(kvp => !string.IsNullOrEmpty(kvp.Value) &&
                            !"signature".Equals(kvp.Key, StringComparison.OrdinalIgnoreCase))
            .OrderBy(kvp => kvp.Key, StringComparer.Ordinal) // ASCII顺序排序
            .Select(kvp => $"{kvp.Key}={kvp.Value}");

        return string.Join("&", validParams);
    }
  • 签名方法

Java 实现

java
/**
     * 签名方法
     * @param content 待签名内容
     * @param privateKey 私钥
     * @return 签名后数据(base64)
     */
    public static String sign(String content, String privateKey) {
        try {
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey.getBytes()));
            KeyFactory factory = KeyFactory.getInstance("RSA");
            PrivateKey priKey = factory.generatePrivate(priPKCS8);
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initSign(priKey);
            signature.update(content.getBytes("UTF-8"));
            byte[] signed = signature.sign();
            return Base64.getEncoder().encodeToString(signed);
        } catch (Exception e) {
            logger.error("sign error", e);
        }
        return null;
    }

.NET 实现

.NET
    public static string Sign(string content, string privateKey, Encoding encoding = null){
            if (string.IsNullOrEmpty(content))
                throw new ArgumentNullException(nameof(content));
            if (string.IsNullOrEmpty(privateKey))
                throw new ArgumentNullException(nameof(privateKey));

            encoding ??= Encoding.UTF8;

            try
            {
                byte[] privateKeyBytes = Convert.FromBase64String(privateKey);
                byte[] dataBytes = encoding.GetBytes(content);

                using var rsa = RSA.Create();
                rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);

                byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                return Convert.ToBase64String(signatureBytes);
            }
            catch (FormatException ex)
            {
                throw new ArgumentException("私钥格式不正确,请提供PKCS#8格式的Base64编码私钥", ex);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"签名失败: {ex.Message}", ex);
            }
    }
  • 验签方法

Java 实现

java
    /**
     * 验证签名
     * @param content 待验签字符串
     * @param sign 签名
     * @param publicKey 公钥
     * @return 验证结果
     */
    public static boolean verify(String content, String sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
            Signature signature = Signature.getInstance("SHA256WithRSA");
            signature.initVerify(pubKey);
            signature.update(content.getBytes("UTF-8"));
            return signature.verify(Base64.getDecoder().decode(sign));
        } catch (Exception e) {
            logger.error("sign error", e);
        }
        return false;
    }

.NET 实现

.NET
public static bool Verify(string content, string signature, string publicKey, Encoding encoding = null)
        {
            if (string.IsNullOrEmpty(content))
                throw new ArgumentNullException(nameof(content));
            if (string.IsNullOrEmpty(signature))
                throw new ArgumentNullException(nameof(signature));
            if (string.IsNullOrEmpty(publicKey))
                throw new ArgumentNullException(nameof(publicKey));

            encoding ??= Encoding.UTF8;

            try
            {
                byte[] publicKeyBytes = Convert.FromBase64String(publicKey);
                byte[] signatureBytes = Convert.FromBase64String(signature);
                byte[] dataBytes = encoding.GetBytes(content);

                using var rsa = RSA.Create();
                rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);

                return rsa.VerifyData(dataBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            }
            catch (FormatException ex)
            {
                throw new ArgumentException("公钥或签名格式不正确", ex);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"验签失败: {ex.Message}", ex);
            }
        }