using Newtonsoft.Json;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using RestSharp;
namespace Api.XYFPay.Helper.Test
{
public class RSA2Helper
{
private static string pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5fdE4X6VCSD3cSmevVne5CCLnSm8tTXt4NwkV1BlG/JfFArV18DyF4xKiVc5Mbs/mflUiXh/CjbWv7F9/qQt17QQbWcoLZpu1yKJTNt5KBNNymv1SYeatXavbFKkpdn3NC+avf1CP1F6F0U3qBXtpVl+AE1ueR3wS3+CrSng5AQRcPmIhDT+jR9Q4NnbaJDQBZ3O+HMtynqbOfPFKF1k/hkPEiMhtDw5BEUyeOMYqXVpL4M9bttcmI8jg3NUddD2joSEwuofAxD4xMlfoY4cPTqdDYpdbWfy7V5CCQdMYgmJ13jZqg17TUTb/lLdK1ajWqEBW9GVCBkcx5Nx2SR8wIDAQAB";
static void Main(string[] args)
{
try
{
var parameters = new Dictionary<string, object>
{
["agetId"] = "61000000340685",
["custId"] = "60000001056272",
["code"] = "282080689138406321",
["orderNo"] = "25102309282349904699test7",
["txamt"] = "1",
["tradingIp"] = "220.249.188.179",
["type"] = "A",
["timeStamp"] = "20251023092824",
["version"] = "1.0.0"
};
// 按键排序字典,包括下划线
var sortedParameters = parameters.OrderBy(p => p.Key, StringComparer.Ordinal)
.Where(x=>x.Value!=null)
.Select(p => p.Value is string ? $"{p.Key}={p.Value}" : $"{p.Key}={JsonConvert.SerializeObject(p.Value)}");
// 拼接排序后的参数
var parameterString = string.Join("&", sortedParameters);
Console.WriteLine("拼接的待签名串:" + parameterString);
// 计算 SHA256 哈希
string sha256Str = SHA256Encrypt(parameterString);
Console.WriteLine("sha256:" + parameterString);
string sign = EncryptByPublicKey(pubKey, sha256Str);
parameters.Add("sign", sign);
// 创建客户端
var options = new RestClientOptions("https://xyf-server-test.postar.cn/yyfsevr/order/scanByMerchant");
var client = new RestClient(options);
var request = new RestRequest();
request.Method = Method.Post;
// 添加 Header
request.AddHeader("Content-Type", "application/json");
request.AddJsonBody(parameters);
RestResponse response = client.Execute(request);
Console.WriteLine("响应参数:" + response.Content);
}
catch (Exception ex)
{
Console.WriteLine($"异常: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
public static string EncryptByPublicKey(string publicKey, string enptStr, string encoding = "UTF-8")
{
String pubKey = publicKey.Trim();
String xmlPublicKey = RSAPublicKeyJava2DotNet(pubKey);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
byte[] cipherbytes;
rsa.FromXmlString(xmlPublicKey);
cipherbytes = rsa.Encrypt(Encoding.GetEncoding(encoding).GetBytes(enptStr), false);
return Convert.ToBase64String(cipherbytes);
}
}
/// <summary>
/// 分段 RSA 公钥解密
/// </summary>
public static byte[] DecryptByPublicKey(string data, string publicKey)
{
byte[] encryptedData = Convert.FromBase64String(data);
string xmlPublicKey = RSAPublicKeyJava2DotNet(publicKey);
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(xmlPublicKey);
int keySizeBytes = rsa.KeySize / 8; // 2048 位 → 256 字节
// 检查长度是否为 keySizeBytes 的整数倍
if (encryptedData.Length % keySizeBytes != 0)
throw new Exception($"密文长度 {encryptedData.Length} 不是 {keySizeBytes} 的整数倍,可能密钥不匹配或数据损坏。");
// 初始化 BouncyCastle 解密器
var keyParam = DotNetUtilities.GetRsaPublicKey(rsa);
IBufferedCipher cipher = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding");
cipher.Init(false, keyParam);
using (var outStream = new MemoryStream())
{
int offset = 0;
while (offset < encryptedData.Length)
{
byte[] block = new byte[keySizeBytes];
Array.Copy(encryptedData, offset, block, 0, keySizeBytes);
byte[] decryptedBlock = cipher.DoFinal(block);
outStream.Write(decryptedBlock, 0, decryptedBlock.Length);
offset += keySizeBytes;
}
return outStream.ToArray();
}
}
}
/// <summary>
/// Java RSA 公钥格式转 .NET 格式
/// </summary>
private static string RSAPublicKeyJava2DotNet(string publicKey)
{
var publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",
Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
}
/// <summary>
/// SHA256 哈希(返回小写十六进制)
/// </summary>
public static string SHA256Encrypt(string data)
{
using (var sha256 = SHA256.Create())
{
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
}<?php
namespace pay;
class Demo
{
private string $apiUrl = "https://xyf-server-test.postar.cn/yyfsevr/order/scanByMerchant";
private const PUBLIC_KEY_BASE64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5fdE4X6VCSD3cSmevVne5CCLnSm8tTXt4NwkV1BlG/JfFArV18DyF4xKiVc5Mbs/mflUiXh/CjbWv7F9/qQt17QQbWcoLZpu1yKJTNt5KBNNymv1SYeatXavbFKkpdn3NC+avf1CP1F6F0U3qBXtpVl+AE1ueR3wS3+CrSng5AQRcPmIhDT+jR9Q4NnbaJDQBZ3O+HMtynqbOfPFKF1k/hkPEiMhtDw5BEUyeOMYqXVpL4M9bttcmI8jg3NUddD2joSEwuofAxD4xMlfoY4cPTqdDYpdbWfy7V5CCQdMYgmJ13jZqg17TUTb/lLdK1ajWqEBW9GVCBkcx5Nx2SR8wIDAQAB";
private function getPublicKey(): string
{
return "-----BEGIN PUBLIC KEY-----\n"
. chunk_split(self::PUBLIC_KEY_BASE64, 64, "\n")
. "-----END PUBLIC KEY-----";
}
/**
* 生成签名(SHA256摘要 + RSA公钥加密)
*/
private function generateSign(array $params): string
{
ksort($params);
$str = '';
foreach ($params as $key => $value) {
if ($value !== null && $value !== '') {
$str .= ($str ? '&' : '') . $key . '=' . $value;
}
}
$sha256 = hash('sha256', $str);
$pubKey = openssl_pkey_get_public($this->getPublicKey());
if ($pubKey === false) {
throw new \RuntimeException('公钥加载失败:' . openssl_error_string());
}
$encrypted = '';
if (!openssl_public_encrypt($sha256, $encrypted, $pubKey)) {
throw new \RuntimeException('签名加密失败:' . openssl_error_string());
}
return base64_encode($encrypted);
}
/**
* 通用请求方法:传入参数数组(不含sign),自动生成sign并POST JSON
*/
public function request(array $params): array
{
$params['sign'] = $this->generateSign($params);
$jsonData = json_encode($params);
$ch = curl_init($this->apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonData)
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 测试环境关闭SSL验证
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError) {
throw new \RuntimeException('cURL错误:' . $curlError);
}
if ($httpCode !== 200) {
throw new \RuntimeException('HTTP错误:' . $httpCode . ', 响应:' . $response);
}
$result = json_decode($response, true);
if ($result === null) {
throw new \RuntimeException('JSON解析失败:' . $response);
}
return $result;
}
//公钥解密参考签名demo
public function decode(string $signature): string
{
$binary = base64_decode($signature, true);
if ($binary === false) {
throw new \InvalidArgumentException('签名Base64解码失败');
}
$blockSize = 256;
$chunks = str_split($binary, $blockSize);
$pubKey = openssl_pkey_get_public($this->getPublicKey());
if ($pubKey === false) {
throw new \RuntimeException('公钥加载失败:' . openssl_error_string());
}
$decrypted = '';
foreach ($chunks as $chunk) {
$decryptedBlock = '';
if (!openssl_public_decrypt($chunk, $decryptedBlock, $pubKey)) {
throw new \RuntimeException('解密块失败:' . openssl_error_string());
}
$decrypted .= $decryptedBlock;
}
return $decrypted;
}
}
// ========== 主函数演示 ==========
function main(): void
{
echo "===== 支付接口请求演示 =====\n\n";
try {
$demo = new \pay\Demo();
// 按您指定的参数构建请求体
$params = [
'agetId' => '61000000340685', // 机构号
'custId' => '60000001050305', // 商户号
'code' => '283646680794562921',
'orderNo' => '202606220000000',
'txamt' => '1',
'type' => 'A',
'tradingIp' => '220.249.188.179',
'timeStamp' => time(), // 当前时间戳(整数)
'version' => '1.0.0'
];
echo "请求参数(不含sign):\n";
print_r($params);
echo "\n";
echo "正在请求接口...\n";
$result = $demo->request($params);
echo "响应结果:\n";
print_r($result);
} catch (\Throwable $e) {
echo "错误:" . $e->getMessage() . "\n";
echo $e->getTraceAsString() . "\n";
}
echo "\n===== 程序结束 =====\n";
}
main();
?>{
"orderNo": "600889899046188256",
"code": "134469628577908003",
"latitude": "22.988558",
"type": "P",
"title": "授权码销售",
"version": "1.0.0",
"timeStamp": "20251119140829",
"agetId": "61000000339134",
"custId": "60000001038669",
"tradingIp": "183.1.65.73",
"detail": {
"goods_detail": [
{
"goods_name": "星群50g口袋鸡胸肉奥尔良味",
"quantity": 1,
"price": 350,
"goods_id": "6939733903026"
}
]
},
"txamt": "350",
"outTime": "15",
"longitude": "113.269323",
"subAppid": "",
"remark": null
}agetId=61000000339134&code=134469628577908003&custId=60000001038669&detail={"goods_detail":[{"goods_name":"星群50g口袋鸡胸肉奥尔良味","quantity":1,"price":350,"goods_id":"6939733903026"}]}&latitude=22.988558&longitude=113.269323&orderNo=600889899046188256&outTime=15&subAppid=&timeStamp=20251119140829&title=授权码销售&tradingIp=183.1.65.73&txamt=350&type=P&version=1.0.0