API签名生成

# 签名规则

所有参数名和参数值参与签名,Key(参数名) 进行排序

  1. 将所有参数的 Key(参数名) 按照 ASCII 排序
  2. 按 Key(参数名) 的顺序,逐个取参数名和参数值进行拼接后,采用 RSA 算法对字符串计算,算出签名字符串

# 示例说明

  • 示例参数
参数名 参数值
merchantCode S820190712000002
orderNum T1231511321515
orderAmount 999.56
callback https://xxx/yyy
timestamp 1745377181
  • 参数格式

格式化规则说明:

  1. 提取传入键值对参数集合(Map)中的所有键,转换为字符串数组并按字典序升序排列。
  2. 遍历排序后的键数组,将每个键及其对应的值以 “键=值&” 的形式依次拼接到字符串中。
  3. 移除拼接后字符串末尾多余的 “&” 符号,最终得到格式化后的参数字符串。
  4. 上述规则已封装到TopPaySignUtil#paramFormat,调用即可得到如下source

source = callback=https://xxx/yyy&merchantCode=S820190712000002&orderAmount=999.56&orderNum=T1231511321515&timestamp=1745377181

  • 计算签名
  1. 使用您在 TopPay商户后台 配置的密钥对
  2. 用您的 私钥(Private Key) 和参数格式化后得到的source,作为入参调用TopPaySignUtil#sign,获取到最终的签名字符串

Sign = Jv0AMYiVSL/V8NxuBf8ZfHn7UyHO8TsU8Xkh2sqa0hbpKH1HSPampNXxzBn5PvJoytb8zPkHuQAMveTuBV5Ye8Qu+n8aw69 ...

# 代码示例

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.RuntimeException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class TopPaySignUtil {

    private static final String RSA = "RSA";
    private static final int MAX_ENCRYPT_BLOCK = 245;  // 加密分块大小‌:ml-citation{ref="4" data="citationList"}
    private static final int MAX_DECRYPT_BLOCK = 256;   // 解密分块大小‌:ml-citation{ref="4" data="citationList"}
    private static final String RSA_ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";

    public static void main(String[] args) throws NoSuchAlgorithmException {
        Map<String,String> params = new HashMap<>() ;
        params.put("merchantCode", "S820190712000002") ;
        params.put("orderAmount", "999.56") ;
        params.put("orderNum", "T1231511321515") ;
        params.put("callback", "https://xxx/yyy") ;
        params.put("timestamp", "1745377181") ;

        String source = paramFormat(params);
        System.out.println("source: " + source);

        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
        keyGen.initialize(2048);
        KeyPair pair = keyGen.generateKeyPair();

        String pubKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
        String priKeyBase64 = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded());

        System.out.println("public key: " + pubKeyBase64);
        System.out.println("private key: " + priKeyBase64);

        String sign = sign(priKeyBase64, source);
        System.out.println("sign: " + sign);

        boolean verify = verify(pubKeyBase64, source, sign);
        System.out.println("verify: " + verify);
    }

    /**
     * 这段代码仅供参考,实际使用需要考虑
     * 1.HttpClient 初始化参与
     * 2.HttpResponse用完需要及时关闭
     */
    public static String doPost(String url, String json) throws IOException {
        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost(url);
        StringEntity s = new StringEntity(json);
        s.setContentEncoding("UTF-8");
        s.setContentType("application/json");
        post.setEntity(s);
        HttpResponse res = client.execute(post);
        if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            return EntityUtils.toString(res.getEntity());// 返回json格式:
        }
        return null;
    }

    //参数格式化
    private static String paramFormat(Map<String, String> param) {
        String[] keys = param.keySet().toArray(new String[0]);
        Arrays.sort(keys); //一定要先排序
        StringBuilder builder = new StringBuilder();
        for (String key : keys) {
            String value = param.get(key);
            if (value != null && !value.trim().isEmpty()) { //需要过滤null和空字符串
                builder.append(key).append("=").append(value).append("&");
            }
        }
        builder.deleteCharAt(builder.length()-1) ;
        return builder.toString();
    }

    /**
     * 计算签名
     * @param priKeyBase64 base64编码的私钥
     * @param source 用于计算签名的源数据
     * @return rsa签名字符串
     */
    public static String sign(String priKeyBase64, String source) {
        return encrypt(source, stringToPrivateKey(priKeyBase64)) ;
    }

    /**
     * 验证签名
     * @param pubKeyBase64 base64编码的公钥
     * @param source 用于计算签名的源数据
     * @param sign 需要验证的签名
     * @return rsa签名字符串
     */
    public static boolean verify(String pubKeyBase64, String source, String sign) {
        return source.equals(decrypt(sign, stringToPublicKey(pubKeyBase64)));
    }

    // RSA私钥加密
    private static String encrypt(String plainData, PrivateKey privateKey) {
        Cipher cipher = null;  // 填充模式‌:ml-citation{ref="1,4" data="citationList"}
        try {
            cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);

            byte[] dataBytes = plainData.getBytes(StandardCharsets.UTF_8);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;

            // 分块加密处理‌:ml-citation{ref="4" data="citationList"}
            while (inputLen - offSet > 0) {
                int blockSize = Math.min(inputLen - offSet, MAX_ENCRYPT_BLOCK);
                byte[] encryptedBlock = cipher.doFinal(dataBytes, offSet, blockSize);
                out.write(encryptedBlock);
                offSet += blockSize;
            }
            return Base64.getEncoder().encodeToString(out.toByteArray());
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | InvalidKeyException |
                 IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("encrypt error.");
        }

    }

    // RSA公钥解密
    private static String decrypt(String encryptedData, PublicKey publicKey) {
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);

            byte[] dataBytes = Base64.getDecoder().decode(encryptedData);
            int inputLen = dataBytes.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;

            // 分块解密处理‌:ml-citation{ref="4" data="citationList"}
            while (inputLen - offSet > 0) {
                int blockSize = Math.min(inputLen - offSet, MAX_DECRYPT_BLOCK);
                byte[] decryptedBlock = cipher.doFinal(dataBytes, offSet, blockSize);
                out.write(decryptedBlock);
                offSet += blockSize;
            }
            return new String(out.toByteArray(), StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException |
                 IOException | InvalidKeyException e) {
            throw new RuntimeException("decrypt error.");
        }

    }


    // 从Base64字符串恢复PublicKey
    public static PublicKey stringToPublicKey(String publicKeyStr) {
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            return keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException("generatePublicKey error.");
        }

    }


    // 从Base64字符串恢复PrivateKey
    public static PrivateKey stringToPrivateKey(String privateKeyStr) {
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException("generatePrivateKey error.");
        }
    }
}


<?php
class TopPaySignUtil
{
    const MAX_ENCRYPT_BLOCK = 245;
    const MAX_DECRYPT_BLOCK = 256;

    public static function main()
    {

        $params = [
            'merchantCode' => 'S820190712000002',
            'orderAmount' => '999.56',
            'orderNum' => 'T1231511321515',
            'callback' => 'https://xxx/yyy',
            'timestamp' => '1745377181'
        ];

        $source = self::paramFormat($params);
        // 生成 RSA 密钥对(与 Java KeyPairGenerator 一样)
        $config = [
            "private_key_bits" => 2048,
            "private_key_type" => OPENSSL_KEYTYPE_RSA,
            // 可能需要 你的openssl.cnf地址
//            'config' => 'D:/phpstudy_pro/Extensions/php/php7.3.4nts/extras/ssl/openssl.cnf',
        ];

        $res = openssl_pkey_new($config);
        openssl_pkey_export($res, $priKeyPem);
        $pubKeyPem = openssl_pkey_get_details($res)['key'];

        if (!openssl_pkey_export($res, $priKeyPem, null, $config)) {
            echo "openssl_pkey_export error: " . openssl_error_string();
        }
        // 转 DER (Java 的 PublicKey.getEncoded() / PrivateKey.getEncoded() 格式)
        $priDer = self::pemToDer($priKeyPem);
        $pubDer = self::pemToDer($pubKeyPem);

        $priKeyBase64 = base64_encode($priDer);
        $pubKeyBase64 = base64_encode($pubDer);
        echo "public key: $pubKeyBase64<br>";
        echo "private key: $priKeyBase64<br>";
        // 私钥加密
        $sign = self::sign($priKeyBase64, $source);
        echo "source: $source<br>";
        echo "sign: $sign<br>";
        // 公钥解密
        $verify = self::verify($pubKeyBase64, $source, $sign);
        echo "verify: " . ($verify ? "true" : "false") . "<br>";
    }

    // ========= Java: paramFormat ===========
    public static function paramFormat($param)
    {
        ksort($param);
        $result = '';
        foreach ($param as $key => $value) {
            if ($value !== null && trim($value) !== '') {
                $result .= $key . "=" . $value . "&";
            }
        }
        return rtrim($result, '&');
    }

    // ============ DER / PEM 转换 ============
    public static function pemToDer($pem)
    {
        return base64_decode(
            preg_replace('/-----(BEGIN|END) (PUBLIC|PRIVATE) KEY-----/', '', trim($pem))
        );
    }

    private static function derToPrivateKey($der)
    {
        $pem = "-----BEGIN PRIVATE KEY-----\n" .
            chunk_split(base64_encode($der), 64, "\n") .
            "-----END PRIVATE KEY-----";
        return openssl_pkey_get_private($pem);
    }

    private static function derToPublicKey($der)
    {
        $pem = "-----BEGIN PUBLIC KEY-----\n" .
            chunk_split(base64_encode($der), 64, "\n") .
            "-----END PUBLIC KEY-----";
        return openssl_pkey_get_public($pem);
    }

    // ============ Java: 私钥加密 =============
    public static function sign($priKeyBase64, $source)
    {
        $privateKey = self::derToPrivateKey(base64_decode($priKeyBase64));
        return self::encryptByPrivateKey($source, $privateKey);
    }

    // ============ Java: 公钥解密 =============
    public static function verify($pubKeyBase64, $source, $sign)
    {
        $publicKey = self::derToPublicKey(base64_decode($pubKeyBase64));
        $decrypt = self::decryptByPublicKey($sign, $publicKey);
        return $decrypt === $source;
    }

    // ============ 私钥加密(与 Java 完全一样)============
    private static function encryptByPrivateKey($data, $key)
    {
        $encrypted = '';
        $chunks = str_split($data, self::MAX_ENCRYPT_BLOCK);

        foreach ($chunks as $chunk) {
            openssl_private_encrypt($chunk, $encryptedChunk, $key, OPENSSL_PKCS1_PADDING);
            $encrypted .= $encryptedChunk;
        }

        return base64_encode($encrypted);
    }

    // ============ 公钥解密(与 Java 完全一样)============
    private static function decryptByPublicKey($data, $key)
    {
        $data = base64_decode($data);
        $chunks = str_split($data, self::MAX_DECRYPT_BLOCK);

        $decrypted = '';
        foreach ($chunks as $chunk) {
            openssl_public_decrypt($chunk, $decryptedChunk, $key, OPENSSL_PKCS1_PADDING);
            $decrypted .= $decryptedChunk;
        }

        return $decrypted;
    }

    public static function doPost($url, $json)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen($json)
        ));
        //调试 不验证证书是否可信
//        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        //调试 不验证证书中的域名是否匹配。
//        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }
}

TopPaySignUtil::main();
?>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Newtonsoft.Json;
using System.IO;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Pkcs;       // ← PrivateKeyInfoFactory、SubjectPublicKeyInfoFactory 在这里
using Org.BouncyCastle.X509;       // ← SubjectPublicKeyInfoFactory 会用到

namespace Demo;

class TopPaySignUtil
{
    private const int MAX_ENCRYPT_BLOCK = 245; 
    private const int MAX_DECRYPT_BLOCK = 256;

    public static async Task Main()
    {
        Dictionary<string, string> paramsDict = new Dictionary<string, string>
        {
            { "merchantCode", "S820190712000002" },
            { "orderAmount", "999.56" },
            { "orderNum", "T1231511321515" },
            { "callback", "https://xxx/yyy" },
            { "timestamp", "1745377181" }
        };

        string source = ParamFormat(paramsDict);
        Console.WriteLine($"source: {source}");

        // ----------- 生成密钥对 ----------
        var rsa = new Org.BouncyCastle.Crypto.Generators.RsaKeyPairGenerator();
        rsa.Init(new Org.BouncyCastle.Crypto.KeyGenerationParameters(new SecureRandom(), 2048));
        var keyPair = rsa.GenerateKeyPair();

        string priKeyBase64 = Convert.ToBase64String(PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private).GetEncoded());
        string pubKeyBase64 = Convert.ToBase64String(SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded());

        Console.WriteLine($"public key: {pubKeyBase64}");
        Console.WriteLine($"private key: {priKeyBase64}");

        // ----------- 私钥加密(java/sign) ----------
        string sign = Sign(priKeyBase64, source);
        Console.WriteLine($"sign: {sign}");

        // ----------- 公钥解密(java/verify) ----------
        bool verify = Verify(pubKeyBase64, source, sign);
        Console.WriteLine($"verify: {verify}");
    }

    // ---------------------- 参数格式化 ----------------------
    public static string ParamFormat(Dictionary<String, String> param)
    {
        var sortedKeys = param.Keys.OrderBy(k => k).ToList();
        StringBuilder builder = new StringBuilder();

        foreach (var key in sortedKeys)
        {
            if (!string.IsNullOrWhiteSpace(param[key]))
                builder.Append($"{key}={param[key]}&");
        }

        if (builder.Length > 0)
            builder.Length--;

        return builder.ToString();
    }

    // ---------------------- Sign(私钥加密) ----------------------
    public static string Sign(string priKeyBase64, string source)
    {
        byte[] data = Encoding.UTF8.GetBytes(source);

        AsymmetricKeyParameter privateKey = PrivateKeyFactory.CreateKey(Convert.FromBase64String(priKeyBase64));

        byte[] encrypted = RsaPrivateEncrypt(privateKey, data);

        return Convert.ToBase64String(encrypted);
    }

    // ---------------------- Verify(公钥解密) ----------------------
    public static bool Verify(string pubKeyBase64, string source, string sign)
    {
        AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(pubKeyBase64));

        byte[] encrypted = Convert.FromBase64String(sign);
        byte[] decrypted = RsaPublicDecrypt(publicKey, encrypted);

        string result = Encoding.UTF8.GetString(decrypted);

        return source.Equals(result);
    }


    // ------------------- 私钥分段加密(兼容 Java) -------------------
    private static byte[] RsaPrivateEncrypt(AsymmetricKeyParameter privateKey, byte[] data)
    {
        IAsymmetricBlockCipher engine = new RsaEngine();
        engine = new Pkcs1Encoding(engine);
        engine.Init(true, privateKey);

        using MemoryStream input = new MemoryStream(data);
        using MemoryStream output = new MemoryStream();

        byte[] buffer = new byte[MAX_ENCRYPT_BLOCK];
        int read;
        while ((read = input.Read(buffer, 0, MAX_ENCRYPT_BLOCK)) > 0)
        {
            byte[] block = engine.ProcessBlock(buffer, 0, read);
            output.Write(block, 0, block.Length);
        }

        return output.ToArray();
    }

    // ------------------- 公钥分段解密(兼容 Java) -------------------
    private static byte[] RsaPublicDecrypt(AsymmetricKeyParameter publicKey, byte[] data)
    {
        IAsymmetricBlockCipher engine = new RsaEngine();
        engine = new Pkcs1Encoding(engine);
        engine.Init(false, publicKey);

        using MemoryStream input = new MemoryStream(data);
        using MemoryStream output = new MemoryStream();

        byte[] buffer = new byte[MAX_DECRYPT_BLOCK];
        int read;
        while ((read = input.Read(buffer, 0, MAX_DECRYPT_BLOCK)) > 0)
        {
            byte[] block = engine.ProcessBlock(buffer, 0, read);
            output.Write(block, 0, block.Length);
        }

        return output.ToArray();
    }

public static async Task<string> DoPost(string url, string json)
    {
        using (HttpClient client = new HttpClient())
        {
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            HttpResponseMessage response = await client.PostAsync(url, content);
            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            return null;
        }
    }

}
package main

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"math/big"
	"net/http"
	"sort"
	"strings"
)

// Java RSA 2048:
// 加密最大 245 字节,解密块固定 256 字节
const (
	MAX_ENCRYPT_BLOCK = 245
)

func main() {
	params := map[string]string{
		"merchantCode": "S820190712000002",
		"orderAmount":  "999.56",
		"orderNum":     "T1231511321515",
		"callback":     "https://xxx/yyy",
		"timestamp":    "1745377181",
	}

	source := paramFormat(params)
	fmt.Println("source:", source)

	// 生成 Java 兼容:X509 公钥 + PKCS8 私钥
	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
	publicKey := &privateKey.PublicKey

	pubDer, _ := x509.MarshalPKIXPublicKey(publicKey)
	pubKeyBase64 := base64.StdEncoding.EncodeToString(pubDer)

	priDer, _ := x509.MarshalPKCS8PrivateKey(privateKey)
	priKeyBase64 := base64.StdEncoding.EncodeToString(priDer)

	fmt.Println("public key:", pubKeyBase64)
	fmt.Println("private key:", priKeyBase64)

	signData := sign(priKeyBase64, source)
	fmt.Println("sign:", signData)

	verifyOK := verify(pubKeyBase64, source, signData)
	fmt.Println("verify:", verifyOK)
}

func paramFormat(param map[string]string) string {
	var keys []string
	for k := range param {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var b strings.Builder
	for _, key := range keys {
		v := param[key]
		if strings.TrimSpace(v) != "" {
			b.WriteString(key + "=" + v + "&")
		}
	}
	return strings.TrimRight(b.String(), "&")
}

//////////////////////////////////////////////////////////////////////
// sign (私钥加密) - Java Cipher.ENCRYPT_MODE 兼容实现
//////////////////////////////////////////////////////////////////////

func sign(privateKeyBase64 string, source string) string {
	priv := stringToPrivateKey(privateKeyBase64)
	return rsaPrivateEncrypt(priv, []byte(source))
}

func verify(pubKeyBase64 string, source string, signData string) bool {
	pub := stringToPublicKey(pubKeyBase64)
	plain := rsaPublicDecrypt(pub, signData)
	return plain == source
}

//////////////////////////////////////////////////////////////////////
// 私钥加密(Java RSA/ECB/PKCS1Padding 完整复刻)
//////////////////////////////////////////////////////////////////////

func rsaPrivateEncrypt(priv *rsa.PrivateKey, data []byte) string {
	var buf bytes.Buffer
	k := priv.Size() // 256 bytes

	for i := 0; i < len(data); i += MAX_ENCRYPT_BLOCK {
		end := i + MAX_ENCRYPT_BLOCK
		if end > len(data) {
			end = len(data)
		}

		block := data[i:end]
		encBlock := rsaRawPrivateEncrypt(priv, block)

		// MUST be exactly 256 bytes
		if len(encBlock) != k {
			panic("encrypted block wrong size")
		}
		buf.Write(encBlock)
	}
	return base64.StdEncoding.EncodeToString(buf.Bytes())
}

//////////////////////////////////////////////////////////////////////
// 公钥解密(Java RSA/ECB/PKCS1Padding 完整复刻)
//////////////////////////////////////////////////////////////////////

func rsaPublicDecrypt(pub *rsa.PublicKey, base64Cipher string) string {
	cipherBytes, _ := base64.StdEncoding.DecodeString(base64Cipher)
	k := pub.Size() // 256 bytes

	if len(cipherBytes)%k != 0 {
		panic("cipher size not aligned to RSA block")
	}

	var buf bytes.Buffer
	for i := 0; i < len(cipherBytes); i += k {
		block := cipherBytes[i : i+k]
		plain := rsaRawPublicDecrypt(pub, block)
		buf.Write(plain)
	}
	return buf.String()
}

//////////////////////////////////////////////////////////////////////
// 底层数学运算:PKCS1Padding + 私钥加密
//////////////////////////////////////////////////////////////////////

func rsaRawPrivateEncrypt(priv *rsa.PrivateKey, data []byte) []byte {
	k := priv.Size()
	if len(data) > k-11 {
		panic("data too large for RSA")
	}

	paddingLen := k - len(data) - 3
	em := make([]byte, k)
	em[0] = 0x00
	em[1] = 0x01
	for i := 2; i < 2+paddingLen; i++ {
		em[i] = 0xFF
	}
	em[2+paddingLen] = 0x00
	copy(em[3+paddingLen:], data)

	m := new(big.Int).SetBytes(em)
	c := new(big.Int).Exp(m, priv.D, priv.N)

	// Java 输出固定 256 bytes
	out := c.Bytes()
	if len(out) < k {
		padded := make([]byte, k)
		copy(padded[k-len(out):], out)
		out = padded
	}
	return out
}

//////////////////////////////////////////////////////////////////////
// 底层数学运算:公钥解密 + 去 PKCS1Padding
//////////////////////////////////////////////////////////////////////

func rsaRawPublicDecrypt(pub *rsa.PublicKey, cipher []byte) []byte {
	k := pub.Size()
	c := new(big.Int).SetBytes(cipher)
	m := new(big.Int).Exp(c, big.NewInt(int64(pub.E)), pub.N)

	out := m.Bytes()
	if len(out) < k {
		padded := make([]byte, k)
		copy(padded[k-len(out):], out)
		out = padded
	}

	em := out
	// PKCS1Padding 解析
	if em[0] != 0x00 || em[1] != 0x01 {
		panic("invalid padding header")
	}

	i := 2
	for ; i < len(em); i++ {
		if em[i] == 0x00 {
			break
		}
	}
	if i == len(em) {
		panic("padding end not found")
	}
	return em[i+1:]
}

//////////////////////////////////////////////////////////////////////
// Base64 → RSA Key 解析
//////////////////////////////////////////////////////////////////////

func stringToPublicKey(b64 string) *rsa.PublicKey {
	der, _ := base64.StdEncoding.DecodeString(b64)
	pub, err := x509.ParsePKIXPublicKey(der)
	if err != nil {
		panic(err)
	}
	return pub.(*rsa.PublicKey)
}

func stringToPrivateKey(b64 string) *rsa.PrivateKey {
	der, _ := base64.StdEncoding.DecodeString(b64)
	key, err := x509.ParsePKCS8PrivateKey(der)
	if err != nil {
		panic(err)
	}
	return key.(*rsa.PrivateKey)
}

//////////////////////////////////////////////////////////////////////
// HTTP POST 封装
//////////////////////////////////////////////////////////////////////

func doPost(url string, json string) (string, error) {
	resp, err := http.Post(url, "application/json", strings.NewReader(json))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	return string(body), nil
}

# !!!注意:不可使用 requests.post(url, json=post_json),否则会对字符串二次序列化导致验签失败
import base64
import requests
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, long_to_bytes


class TopPaySignUtil:
  MAX_ENCRYPT_BLOCK = 245
  MAX_DECRYPT_BLOCK = 256

  @staticmethod
  def main():
    params = {
      'merchantCode': 'S820190712000002',
      'orderAmount': '999.56',
      'orderNum': 'T1231511321515',
      'callback': 'https://xxx/yyy',
      'timestamp': '1745377181'
    }

    source = TopPaySignUtil.param_format(params)
    print(f"source: {source}")

    # 生成与 Java 兼容的密钥:X509 公钥 + PKCS8 私钥(DER Base64)
    key = RSA.generate(2048)
    pub_key_base64 = base64.b64encode(key.publickey().export_key(format='DER')).decode()
    pri_key_base64 = base64.b64encode(key.export_key(format='DER', pkcs=8)).decode()

    print(f"public key: {pub_key_base64}")
    print(f"private key: {pri_key_base64}")

    sign = TopPaySignUtil.sign(pri_key_base64, source)
    print(f"sign: {sign}")

    verify = TopPaySignUtil.verify(pub_key_base64, source, sign)
    print(f"verify: {verify}")

  @staticmethod
  def do_post(url, post_json):
    # post_json 为 json.dumps 后的字符串,与 Java TopPaySignUtil.doPost 一致
    # 注意:不可使用 requests.post(url, json=post_json),否则会对字符串二次序列化导致验签失败
    response = requests.post(
      url,
      data=post_json.encode('utf-8'),
      headers={'Content-Type': 'application/json'},
    )
    if response.status_code == 200:
      return response.text
    return None

  @staticmethod
  def param_format(param):
    sorted_keys = sorted(param.keys())
    pairs = []
    for key in sorted_keys:
      value = param.get(key)
      if value is not None and str(value).strip():
        pairs.append(f"{key}={value}")
    return "&".join(pairs)

  @staticmethod
  def sign(pri_key_base64, source):
    private_key = TopPaySignUtil.string_to_private_key(pri_key_base64)
    return TopPaySignUtil.encrypt(source, private_key)

  @staticmethod
  def verify(pub_key_base64, source, sign):
    public_key = TopPaySignUtil.string_to_public_key(pub_key_base64)
    return source == TopPaySignUtil.decrypt(sign, public_key)

  @staticmethod
  def encrypt(plain_data, private_key):
    plain_bytes = plain_data.encode('utf-8')
    encrypted_data = b''
    for i in range(0, len(plain_bytes), TopPaySignUtil.MAX_ENCRYPT_BLOCK):
      chunk = plain_bytes[i:i + TopPaySignUtil.MAX_ENCRYPT_BLOCK]
      encrypted_data += TopPaySignUtil._rsa_private_encrypt(private_key, chunk)
    return base64.b64encode(encrypted_data).decode()

  @staticmethod
  def decrypt(encrypted_data, public_key):
    encrypted_bytes = base64.b64decode(encrypted_data)
    block_size = public_key.size_in_bytes()
    decrypted_data = b''
    for i in range(0, len(encrypted_bytes), block_size):
      chunk = encrypted_bytes[i:i + block_size]
      decrypted_data += TopPaySignUtil._rsa_public_decrypt(public_key, chunk)
    return decrypted_data.decode('utf-8')

  @staticmethod
  def string_to_public_key(public_key_str):
    return RSA.import_key(base64.b64decode(public_key_str))

  @staticmethod
  def string_to_private_key(private_key_str):
    return RSA.import_key(base64.b64decode(private_key_str))

  @staticmethod
  def _pkcs1_pad_type1(data, block_size):
    padding_len = block_size - len(data) - 3
    if padding_len < 8:
      raise ValueError('data too large for RSA block')
    return b'\x00\x01' + (b'\xff' * padding_len) + b'\x00' + data

  @staticmethod
  def _pkcs1_unpad_type1(em):
    if len(em) < 11 or em[0] != 0x00 or em[1] != 0x01:
      raise ValueError('invalid padding header')
    i = 2
    while i < len(em) and em[i] != 0x00:
      if em[i] != 0xFF:
        raise ValueError('invalid padding')
      i += 1
    if i >= len(em):
      raise ValueError('padding end not found')
    return em[i + 1:]

  @staticmethod
  def _rsa_private_encrypt(private_key, data):
    k = private_key.size_in_bytes()
    em = TopPaySignUtil._pkcs1_pad_type1(data, k)
    m = bytes_to_long(em)
    c = pow(m, private_key.d, private_key.n)
    return long_to_bytes(c, k)

  @staticmethod
  def _rsa_public_decrypt(public_key, cipher_block):
    k = public_key.size_in_bytes()
    c = bytes_to_long(cipher_block)
    m = pow(c, public_key.e, public_key.n)
    em = long_to_bytes(m, k)
    return TopPaySignUtil._pkcs1_unpad_type1(em)


if __name__ == "__main__":
  TopPaySignUtil.main()

/**
 * 完全兼容 Java TopPaySignUtil 的 Node.js 版本
 * 原理:RSA 私钥加密(非 SHA256withRSA)
 */

const crypto = require('crypto');
const fs = require('fs');
const https = require('https');

class TopPaySignUtil {
    static MAX_ENCRYPT_BLOCK = 245;
    static MAX_DECRYPT_BLOCK = 256;

    static main() {
        const params = {
            merchantCode: 'S820190712000002',
            orderAmount: '999.56',
            orderNum: 'T1231511321515',
            callback: 'https://xxx/yyy',
            timestamp: '1745377181'
        };

        const source = this.paramFormat(params);
        console.log(`source: ${source}`);

        // === 生成与 Java 兼容的 RSA 密钥对 ===
        const {publicKey, privateKey} = crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            publicKeyEncoding: {
                type: 'spki',   // Java 默认公钥格式
                format: 'pem'
            },
            privateKeyEncoding: {
                type: 'pkcs8',  // Java 默认私钥格式
                format: 'pem'
            }
        });

        const pubKeyPem = publicKey;
        const priKeyPem = privateKey;

        console.log(`public key (SPKI):\n${pubKeyPem}`);
        console.log(`private key (PKCS8):\n${priKeyPem}`);

        // === 签名 ===
        const sign = this.sign(priKeyPem, source);
        console.log(`sign: ${sign}`);

        // === 验签 ===
        const verify = this.verify(pubKeyPem, source, sign);
        console.log(`verify: ${verify}`);
    }

    // ========== 网络 POST 请求函数 ==========
    static doPost(url, json) {
        return new Promise((resolve, reject) => {
            const options = {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Content-Length': Buffer.byteLength(json)
                }
            };

            const req = https.request(url, options, res => {
                let data = '';
                res.on('data', chunk => data += chunk);
                res.on('end', () => {
                    if (res.statusCode === 200) {
                        resolve(data);
                    } else {
                        reject(new Error(`Request failed with status code ${res.statusCode}`));
                    }
                });
            });

            req.on('error', error => reject(error));
            req.write(json);
            req.end();
        });
    }

    // ========== 参数格式化 ==========
    static paramFormat(param) {
        const sortedKeys = Object.keys(param).sort();
        const pairs = [];
        for (const key of sortedKeys) {
            const value = param[key];
            if (value !== null && value !== undefined && value.toString().trim() !== '') {
                pairs.push(`${key}=${value}`);
            }
        }
        return pairs.join('&');
    }

    // ========== 签名(RSA 私钥加密) ==========
    static sign(privateKeyPem, source) {
        return this.encrypt(source, this.stringToPrivateKey(privateKeyPem));
    }

    // ========== 验签(RSA 公钥解密) ==========
    static verify(publicKeyPem, source, sign) {
        const decrypted = this.decrypt(sign, this.stringToPublicKey(publicKeyPem));
        return source === decrypted;
    }

    // ========== RSA 私钥加密(Java 对应 encrypt()) ==========
    static encrypt(plainData, privateKey) {
        const buffer = Buffer.from(plainData, 'utf8');
        const chunks = [];
        for (let offset = 0; offset < buffer.length; offset += this.MAX_ENCRYPT_BLOCK) {
            const chunk = buffer.slice(offset, offset + this.MAX_ENCRYPT_BLOCK);
            const encrypted = crypto.privateEncrypt(
                {key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING},
                chunk
            );
            chunks.push(encrypted);
        }
        return Buffer.concat(chunks).toString('base64');
    }

    // ========== RSA 公钥解密(Java 对应 decrypt()) ==========
    static decrypt(base64Data, publicKey) {
        const buffer = Buffer.from(base64Data, 'base64');
        const chunks = [];
        for (let offset = 0; offset < buffer.length; offset += this.MAX_DECRYPT_BLOCK) {
            const chunk = buffer.slice(offset, offset + this.MAX_DECRYPT_BLOCK);
            const decrypted = crypto.publicDecrypt(
                {key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING},
                chunk
            );
            chunks.push(decrypted);
        }
        return Buffer.concat(chunks).toString('utf8');
    }

    // ========== PEM 字符串转公钥/私钥 ==========
    static stringToPublicKey(publicKeyStr) {
        return crypto.createPublicKey(publicKeyStr);
    }

    static stringToPrivateKey(privateKeyStr) {
        return crypto.createPrivateKey(privateKeyStr);
    }
}
//todo RSA生成
// TopPaySignUtil.main();


//==========您更喜欢面向过程编程!???===============================









    // ========== PEM 字符串转公钥/私钥 ==========

// 生成 RSA 密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048, // 密钥长度(Java 常用 2048)
    publicKeyEncoding: {
        type: 'spki', // 公钥格式(Java 默认)
        format: 'pem', // 输出 PEM 格式
    },
    privateKeyEncoding: {
        type: 'pkcs8', // 私钥格式(Java 默认)
        format: 'pem', // 输出 PEM 格式
    },
});

// 输出到控制台
console.log('-----BEGIN 公钥 (SPKI, Java 兼容) -----\n');
console.log(publicKey);
console.log('-----BEGIN 私钥 (PKCS8, Java 兼容) -----\n');
console.log(privateKey);

// 可选:保存到文件
fs.writeFileSync('private_key_pkcs8.pem', privateKey);
console.log('\n✅ 已生成并保存为:');
console.log('  private_key_pkcs8.pem');
fs.writeFileSync('public_key_spki.pem', publicKey);
console.log('  public_key_spki.pem');


const params = {
    merchantCode: 'S820190712000002',
    orderNum: 'T16425931668144',
    orderAmount: '888',
    callback: 'https://xxx/yyy',
    timestamp: Math.floor(Date.now() / 1000), // 秒级时间戳
};
// 1️⃣ 按 key 排序并拼接为 key=value&key=value 格式
function getSortedQueryString(params) {
    return Object.keys(params)
        .filter(k => params[k] !== undefined && params[k] !== null && params[k] !== "")
        .sort()
        .map(key => `${key}=${params[key]}`)
        .join('&');
}

// 2️⃣ RSA 私钥加密(对应 Java encrypt())
function rsaPrivateEncrypt(data, privateKeyPem) {
    const buffer = Buffer.from(data, 'utf8');
    const maxBlock = 245; // Java 中的 MAX_ENCRYPT_BLOCK
    const chunks = [];
    for (let offset = 0; offset < buffer.length; offset += maxBlock) {
        const chunk = buffer.slice(offset, offset + maxBlock);
        const encrypted = crypto.privateEncrypt(
            {
                key: privateKeyPem,
                padding: crypto.constants.RSA_PKCS1_PADDING,
            },
            chunk
        );
        chunks.push(encrypted);
    }
    return Buffer.concat(chunks).toString('base64');
}

// 3️⃣ RSA 公钥解密(对应 Java decrypt())
function rsaPublicDecrypt(base64Data, publicKeyPem) {
    const buffer = Buffer.from(base64Data, 'base64');
    const maxBlock = 256;
    const chunks = [];
    for (let offset = 0; offset < buffer.length; offset += maxBlock) {
        const chunk = buffer.slice(offset, offset + maxBlock);
        const decrypted = crypto.publicDecrypt(
            {
                key: publicKeyPem,
                padding: crypto.constants.RSA_PKCS1_PADDING,
            },
            chunk
        );
        chunks.push(decrypted);
    }
    return Buffer.concat(chunks).toString('utf8');
}

const sourceData = getSortedQueryString(params);
console.log('待签名字符串:', sourceData);

// 生成 Java 等价签名(RSA 私钥加密)
const sign = rsaPrivateEncrypt(sourceData, privateKey);
console.log('生成的签名 (Base64):', sign);

// 验证签名逻辑一致性
const verifyResult = rsaPublicDecrypt(sign, publicKey);
console.log('本地验签结果是否一致:', verifyResult === sourceData);