API Call Description
I. Interface Request:
Interface parameters
| Sequence Number | Domain Name | Variable Name | Required | Type | Remarks |
|---|---|---|---|---|---|
| 1 | Version Number | version | M | String(6) | 1.0.0 |
| 2 | Merchant Number | userNo | M | Number | Unique number provided to merchants |
| 3 | Type | dataType | M | String(8) | Business Type |
| 4 | Datagram | dataContent | M | String(2048) | Data Content |
- Convert dataContent to a JSON string
- Base64 encode a JSON string
- Use RSA 1024 to asymmetrically encrypt the Base64-encoded string into a byte array
- Convert a byte array to a hexadecimal string
2. Interface Return:
| Sequence Number | Domain Name | Variable Name | Required | Type | Remarks |
|---|---|---|---|---|---|
| 1 | Success Identifier | success | M | boolean | Return true (success) or false |
| 2 | Error Code | errorCode | O | String(6) | success is false, errorCode has a value. For details, see the error code description. |
| 3 | Error Description | errorMsg | O | String(256) | Error description, not empty when success is false |
| 4 | Return result information | result | O | String(1024) | SUCCESS is empty when it is false, and not empty otherwise |
| 5 | Merchant Number | userNo | M | String(11) | Unique number provided to merchants |
| 6 | Is it asynchronous | async | M | Boolean | True indicates that the callback notification result will be called in business processing, false indicates synchronous processing, and result |
1、The returned data format is the same as that of the merchant's JSON. After decrypting with the public key, and then decoding Base64, you can obtain it.
3. Interface Return:Encryption and decryption process diagram
4. Encrypt demo:
/**
rsa private key: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKwiNo11h/VKCYedT0VdxxalCwhfCt79oGO5FxUmZiYUoiXAOHGrQWLElKZ2ezmwna5or0IFkpbhedtQ7SrrGtc/S9An2eKehoMVXU3tVEE27rVnOPC4FqEomZ5h1VJTeGALxlaKnF5lI65pVQ3q3V2YsMx4vmSWKNaf9HTuo9bzAgMBAAECgYAD+KYZjWSdnB+sKUzy5L77HsOqZcbybheNNW/65O/mYQN8q3qh5LmVdcOYM5OUOSbqJzAj7cz7/ie5j5xpKRNtahw9vJsl321PxjiQDsai6W2TwQdNestrbX2o9FEMa59OwUfzAf4lynJ8xjdA2B/3QI08JvqaAZ6MndNCBEoIQQJBAO62gtchvVirikbUU2D4un+hmdU9Uj0g+gFBRGnsTmFCBiL9v0aqiD76J0Aa8gVKXZXvOyBxbKLLVLfLNGUlLt8CQQC4mWQyxUVYR3eIC01I2pAvxMfT1fIao1RgDMNI10g8FBYVs4wZwnc7yX2PfKNzqMcIxNW1+NKqINqQI9xhh15tAkAhOAS9K1TOIhD8ClAQDozldfeSVRY8q3oe8pYyp0/A+Q8hj24ux0xudyE/KoDDe7XKR6BSw3X6sZD4gq6n5KTBAkAS8fsskr5pLvx/g9lsrrG5lVKE1SJBxZ11NhocsauCLvWNSJ4KTsD569XtEfeceSfkKH9ea6kDONf1jxihEcmJAkEAjbn2HzUtg+r2dkadYJ5k7hVmcNeVlrC8GpclEPJh3OBbc8VxCcIzWvH/LRm3o2lMcdTfj4jjQSMxwRilPydRWg==
before encrypt request param: {"userReqNo":"2024493320640815396"}
encrypt data: 9be02077116ef9026ab7a4def4276bb8eeac12e197c65263fec0adbae0cb626286e5ed77bab56efe6f69f150d59e6c5843b713dc26ed94bbbcb408cfc5c2aa2ddd0752ecfcde3a4c9823e8d9f91684c71f1dafac00c5dd4b7ba7e5f6bf183945928f720e4ac725fd4bc8621d86193f0ef416343606cf7229f67a33b8ee4b5578
request body: {"dataContent":"9be02077116ef9026ab7a4def4276bb8eeac12e197c65263fec0adbae0cb626286e5ed77bab56efe6f69f150d59e6c5843b713dc26ed94bbbcb408cfc5c2aa2ddd0752ecfcde3a4c9823e8d9f91684c71f1dafac00c5dd4b7ba7e5f6bf183945928f720e4ac725fd4bc8621d86193f0ef416343606cf7229f67a33b8ee4b5578","dataType":"json","userNo":"2024493320640815396","version":"1.0.0"}
**/
4. Decrypt demo:
/**
rsa public key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjHk1MSNR5yEBilpX41JIS9a/jB9GeL7IhLv3ycGh4drOtz+s7h0l3+dCuM+qI2zLY/sxw9I3J91BPA424mWV0uoyiXpEP8vhk0PS+UoYGsqbPwlLjfzf0e0VkI1eg4muZ89l8jKODsiYEsu76WmSZp3D3ezyZAbp6rfSly9HCNwIDAQAB
response body: {"result":"d235790f2b14ccb0f4e068367147c7ac694ae3216a208dc45ae5dfbb12547dedc18abbb9c117bf6f005b10f7c8234fd91746f17d17f3c64b905049540d841f5319589b1c80e3f09a303e03655c4f46bfcff40b4d0a03f8d2120b6278713c02c8b69c3318e6fd66988dccd82435259a7826e7b30340c7907350981c5b1b1dea86","async":false,"success":true,"userNo":"2024606286272130274"}
before decrypt response data: d235790f2b14ccb0f4e068367147c7ac694ae3216a208dc45ae5dfbb12547dedc18abbb9c117bf6f005b10f7c8234fd91746f17d17f3c64b905049540d841f5319589b1c80e3f09a303e03655c4f46bfcff40b4d0a03f8d2120b6278713c02c8b69c3318e6fd66988dccd82435259a7826e7b30340c7907350981c5b1b1dea86
decrypt result: {"userReqNo": "2024493320640815396"}
**/
5. JAVA Encryption/Decryption code:
import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fornax.demo.util.FormatUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class EncryptTestCase {
private static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKwiNo11h/VKCYedT0VdxxalCwhfCt79oGO5FxUmZiYUoiXAOHGrQWLElKZ2ezmwna5or0IFkpbhedtQ7SrrGtc/S9An2eKehoMVXU3tVEE27rVnOPC4FqEomZ5h1VJTeGALxlaKnF5lI65pVQ3q3V2YsMx4vmSWKNaf9HTuo9bzAgMBAAECgYAD+KYZjWSdnB+sKUzy5L77HsOqZcbybheNNW/65O/mYQN8q3qh5LmVdcOYM5OUOSbqJzAj7cz7/ie5j5xpKRNtahw9vJsl321PxjiQDsai6W2TwQdNestrbX2o9FEMa59OwUfzAf4lynJ8xjdA2B/3QI08JvqaAZ6MndNCBEoIQQJBAO62gtchvVirikbUU2D4un+hmdU9Uj0g+gFBRGnsTmFCBiL9v0aqiD76J0Aa8gVKXZXvOyBxbKLLVLfLNGUlLt8CQQC4mWQyxUVYR3eIC01I2pAvxMfT1fIao1RgDMNI10g8FBYVs4wZwnc7yX2PfKNzqMcIxNW1+NKqINqQI9xhh15tAkAhOAS9K1TOIhD8ClAQDozldfeSVRY8q3oe8pYyp0/A+Q8hj24ux0xudyE/KoDDe7XKR6BSw3X6sZD4gq6n5KTBAkAS8fsskr5pLvx/g9lsrrG5lVKE1SJBxZ11NhocsauCLvWNSJ4KTsD569XtEfeceSfkKH9ea6kDONf1jxihEcmJAkEAjbn2HzUtg+r2dkadYJ5k7hVmcNeVlrC8GpclEPJh3OBbc8VxCcIzWvH/LRm3o2lMcdTfj4jjQSMxwRilPydRWg==";
private static final String PLATFORM_PUB_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjHk1MSNR5yEBilpX41JIS9a/jB9GeL7IhLv3ycGh4drOtz+s7h0l3+dCuM+qI2zLY/sxw9I3J91BPA424mWV0uoyiXpEP8vhk0PS+UoYGsqbPwlLjfzf0e0VkI1eg4muZ89l8jKODsiYEsu76WmSZp3D3ezyZAbp6rfSly9HCNwIDAQAB";
@Test
@SneakyThrows
public void encryptByStringPrivateKeyTest(){
Map<String,String> req = new HashMap<>();
req.put("dataType","json");
req.put("version","1.0.0");
req.put("userNo","2024493320640815396");
Map<String,String> data = new HashMap<>();
data.put("userReqNo","2024493320640815396");
System.out.println("rsa private key: " + PRIVATE_KEY);
System.out.println("before encrypt request param: " + JSON.toJSONString(data));
String encryptData = byte2Hex(encryptToBytes(Base64.encode(JSON.toJSONString(data)), getPrivateKey(PRIVATE_KEY)));
System.out.println("encrypt data: " + encryptData);
req.put("dataContent", encryptData);
System.out.println("request body: " + JSON.toJSONString(req));
}
@Test
@SneakyThrows
public void decryptByStringPublicKeyTest(){
String str = "{\"result\":\"d235790f2b14ccb0f4e068367147c7ac694ae3216a208dc45ae5dfbb12547dedc18abbb9c117bf6f005b10f7c8234fd91746f17d17f3c64b905049540d841f5319589b1c80e3f09a303e03655c4f46bfcff40b4d0a03f8d2120b6278713c02c8b69c3318e6fd66988dccd82435259a7826e7b30340c7907350981c5b1b1dea86\",\"async\":false,\"success\":true,\"userNo\":\"2024606286272130274\"}";
JSONObject jsonObject = JSON.parseObject(str);
System.out.println("rsa public key: " + PLATFORM_PUB_KEY);
System.out.println("response body: " + str);
System.out.println("before decrypt response data: " + jsonObject.getString("result"));
String result = decryptToString(jsonObject.getString("result"), getPublicKey(PLATFORM_PUB_KEY));
System.out.println("decrypt result: " + result);
}
public static String byte2Hex(byte[] srcBytes) {
StringBuilder hexRetSB = new StringBuilder();
for (byte b : srcBytes) {
String hexString = Integer.toHexString(0x00ff & b);
hexRetSB.append(hexString.length() == 1 ? 0 : "").append(hexString);
}
return hexRetSB.toString();
}
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
Provider provider = Security.getProvider("SunRsaSign");
KeyFactory keyFactory = KeyFactory.getInstance("RSA",provider);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(java.util.Base64.getDecoder().decode(publicKey));
return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
}
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
Provider provider = Security.getProvider("SunRsaSign");
KeyFactory keyFactory = KeyFactory.getInstance("RSA",provider);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(java.util.Base64.getDecoder().decode(privateKey));
return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
}
public static byte[] encryptToBytes(String content, PrivateKey privateKey) {
try {
return rsaByPrivateKey(content.getBytes(), privateKey, Cipher.ENCRYPT_MODE);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static byte[] rsaByPrivateKey(byte[] srcData, PrivateKey privateKey, int mode) {
try {
Provider provider = Security.getProvider("SunJCE");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding",provider);
cipher.init(mode, privateKey);
// Segmented encryption
int blockSize = (mode == Cipher.ENCRYPT_MODE) ? cipher.getOutputSize(srcData.length) - 11
: cipher.getOutputSize(srcData.length);
byte[] decryptData = null;
for (int i = 0; i < srcData.length; i += blockSize) {
byte[] doFinal = cipher.doFinal(subarray(srcData, i, i + blockSize));
decryptData = addAll(decryptData, doFinal);
}
return decryptData;
} catch (Exception e) {
log.error(e.getMessage(),e);
}
return null;
}
public static String decryptToString(String content, PublicKey publicKey) {
try {
byte[] destBytes = rsaByPublicKey(FormatUtil.hex2Bytes(content), publicKey, Cipher.DECRYPT_MODE);
if (destBytes == null) {
return null;
}
String s = new String(destBytes, StandardCharsets.UTF_8);
String s1 = s.replaceAll("\r|\n", "");
return new String(java.util.Base64.getDecoder().decode(s1), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SneakyThrows
public static byte[] rsaByPublicKey(byte[] srcData, PublicKey publicKey, int mode) {
try {
Provider provider = Security.getProvider("SunJCE");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding",provider);
cipher.init(mode, publicKey);
// Segmented encryption
int blockSize = (mode == Cipher.ENCRYPT_MODE) ? cipher.getOutputSize(srcData.length) - 11
: cipher.getOutputSize(srcData.length);
byte[] encryptedData = null;
for (int i = 0; i < srcData.length; i += blockSize) {
byte[] doFinal = cipher.doFinal(subarray(srcData, i, i + blockSize));
encryptedData = addAll(encryptedData, doFinal);
}
return encryptedData;
} catch (Exception e) {
log.error(e.getMessage(),e);
}
return null;
}
public static byte[] subarray(byte[] array, int startIndexInclusive, int endIndexExclusive) {
if (array == null) {
return null;
}
if (startIndexInclusive < 0) {
startIndexInclusive = 0;
}
if (endIndexExclusive > array.length) {
endIndexExclusive = array.length;
}
int newSize = endIndexExclusive - startIndexInclusive;
if (newSize <= 0) {
return new byte[0];
}
byte[] subarray = new byte[newSize];
System.arraycopy(array, startIndexInclusive, subarray, 0, newSize);
return subarray;
}
public static byte[] addAll(byte[] array1, byte[] array2) {
if (array1 == null) {
return clone(array2);
} else if (array2 == null) {
return clone(array1);
}
byte[] joinedArray = new byte[array1.length + array2.length];
System.arraycopy(array1, 0, joinedArray, 0, array1.length);
System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
return joinedArray;
}
public static byte[] clone(byte[] array) {
if (array == null) {
return null;
}
return array.clone();
}
}
6. PHP Encryption/Decryption code:
//encrypt
<?php
$public_key_string = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKwiNo11h/VKCYedT0VdxxalCwhfCt79oGO5FxUmZiYUoiXAOHGrQWLElKZ2ezmwna5or0IFkpbhedtQ7SrrGtc/S9An2eKehoMVXU3tVEE27rVnOPC4FqEomZ5h1VJTeGALxlaKnF5lI65pVQ3q3V2YsMx4vmSWKNaf9HTuo9bzAgMBAAECgYAD+KYZjWSdnB+sKUzy5L77HsOqZcbybheNNW/65O/mYQN8q3qh5LmVdcOYM5OUOSbqJzAj7cz7/ie5j5xpKRNtahw9vJsl321PxjiQDsai6W2TwQdNestrbX2o9FEMa59OwUfzAf4lynJ8xjdA2B/3QI08JvqaAZ6MndNCBEoIQQJBAO62gtchvVirikbUU2D4un+hmdU9Uj0g+gFBRGnsTmFCBiL9v0aqiD76J0Aa8gVKXZXvOyBxbKLLVLfLNGUlLt8CQQC4mWQyxUVYR3eIC01I2pAvxMfT1fIao1RgDMNI10g8FBYVs4wZwnc7yX2PfKNzqMcIxNW1+NKqINqQI9xhh15tAkAhOAS9K1TOIhD8ClAQDozldfeSVRY8q3oe8pYyp0/A+Q8hj24ux0xudyE/KoDDe7XKR6BSw3X6sZD4gq6n5KTBAkAS8fsskr5pLvx/g9lsrrG5lVKE1SJBxZ11NhocsauCLvWNSJ4KTsD569XtEfeceSfkKH9ea6kDONf1jxihEcmJAkEAjbn2HzUtg+r2dkadYJ5k7hVmcNeVlrC8GpclEPJh3OBbc8VxCcIzWvH/LRm3o2lMcdTfj4jjQSMxwRilPydRWg==";
$private_key_pem = "-----BEGIN PRIVATE KEY-----\n" . wordwrap($public_key_string, 64, "\n", true) . "\n-----END PRIVATE KEY-----";
echo $private_key_pem;
$plain_data = '{"userReqNo":"2024493320640815396"}';
$bytes = base64_encode($plain_data);
$segment_size = 117;
$encrypted_base_64 = '';
for ($i = 0; $i < strlen($bytes); $i += $segment_size) {
$segment = substr($bytes, $i, $segment_size);
$partial_decrypted = '';
if (openssl_private_encrypt($segment, $partial_decrypted, $private_key_pem, OPENSSL_PKCS1_PADDING)) {
$encrypted_base_64 .= $partial_decrypted;
} else {
echo "encrypt failed";
break;
}
}
$data = bin2hex($encrypted_base_64);
echo "final result: $data"
?>
//decrypt
<?php
$public_key_string = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjHk1MSNR5yEBilpX41JIS9a/jB9GeL7IhLv3ycGh4drOtz+s7h0l3+dCuM+qI2zLY/sxw9I3J91BPA424mWV0uoyiXpEP8vhk0PS+UoYGsqbPwlLjfzf0e0VkI1eg4muZ89l8jKODsiYEsu76WmSZp3D3ezyZAbp6rfSly9HCNwIDAQAB";
$public_key_pem = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($public_key_string, 64, "\n", true) . "\n-----END PUBLIC KEY-----";
echo $public_key_pem;
$encrypted_data = 'd235790f2b14ccb0f4e068367147c7ac694ae3216a208dc45ae5dfbb12547dedc18abbb9c117bf6f005b10f7c8234fd91746f17d17f3c64b905049540d841f5319589b1c80e3f09a303e03655c4f46bfcff40b4d0a03f8d2120b6278713c02c8b69c3318e6fd66988dccd82435259a7826e7b30340c7907350981c5b1b1dea86';
$bytes = hex2bin($encrypted_data);
$segment_size = 128;
$decrypted_base_64 = '';
for ($i = 0; $i < strlen($bytes); $i += $segment_size) {
$segment = substr($bytes, $i, $segment_size);
$partial_decrypted = '';
if (openssl_public_decrypt($segment, $partial_decrypted, $public_key_pem, OPENSSL_PKCS1_PADDING)) {
$decrypted_base_64 .= $partial_decrypted;
} else {
echo "decrypt failed";
break;
}
}
echo "base64: $decrypted_base_64";
$data = base64_decode($decrypted_base_64);
echo "final result: $data"
?>
7. Python Encryption/Decryption code:
//encrypt
import base64
import binascii
import rsa
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from textwrap import wrap
from rsa import pkcs1, core, transform, PrivateKey, PublicKey, sign, verify, VerificationError
def _chunk_data(data, chunk_size):
"""Yield successive chunks from the input data."""
for i in range(0, len(data), chunk_size):
yield data[i:i + chunk_size]
private_key_string = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKwiNo11h/VKCYedT0VdxxalCwhfCt79oGO5FxUmZiYUoiXAOHGrQWLElKZ2ezmwna5or0IFkpbhedtQ7SrrGtc/S9An2eKehoMVXU3tVEE27rVnOPC4FqEomZ5h1VJTeGALxlaKnF5lI65pVQ3q3V2YsMx4vmSWKNaf9HTuo9bzAgMBAAECgYAD+KYZjWSdnB+sKUzy5L77HsOqZcbybheNNW/65O/mYQN8q3qh5LmVdcOYM5OUOSbqJzAj7cz7/ie5j5xpKRNtahw9vJsl321PxjiQDsai6W2TwQdNestrbX2o9FEMa59OwUfzAf4lynJ8xjdA2B/3QI08JvqaAZ6MndNCBEoIQQJBAO62gtchvVirikbUU2D4un+hmdU9Uj0g+gFBRGnsTmFCBiL9v0aqiD76J0Aa8gVKXZXvOyBxbKLLVLfLNGUlLt8CQQC4mWQyxUVYR3eIC01I2pAvxMfT1fIao1RgDMNI10g8FBYVs4wZwnc7yX2PfKNzqMcIxNW1+NKqINqQI9xhh15tAkAhOAS9K1TOIhD8ClAQDozldfeSVRY8q3oe8pYyp0/A+Q8hj24ux0xudyE/KoDDe7XKR6BSw3X6sZD4gq6n5KTBAkAS8fsskr5pLvx/g9lsrrG5lVKE1SJBxZ11NhocsauCLvWNSJ4KTsD569XtEfeceSfkKH9ea6kDONf1jxihEcmJAkEAjbn2HzUtg+r2dkadYJ5k7hVmcNeVlrC8GpclEPJh3OBbc8VxCcIzWvH/LRm3o2lMcdTfj4jjQSMxwRilPydRWg=="
private_key_pem = "-----BEGIN PRIVATE KEY-----\n" + "\n".join(wrap(private_key_string, 64)) + "\n-----END PRIVATE KEY-----"
# private_key_pem = f"-----BEGIN PRIVATE KEY-----\n{private_key_string}\n-----END PRIVATE KEY-----"
print(private_key_pem)
try:
key = RSA.importKey(private_key_pem)
except ValueError as e:
print(f"load private key: {e}")
exit()
public_key = key.publickey()
cipher_encrypt = PKCS1_v1_5.new(public_key)
key_length = key.n.bit_length() // 8
segment_size = key_length - 11 # 117
encrypted_base_64 = b''
# print(dir(key))
plain_data = '{"userReqNo":"2024493320640815396"}'
php_style_input = base64.b64encode(plain_data.encode('utf-8'))
print(f"1. before encrypt data (Hex): {php_style_input.hex()}")
for chunk in _chunk_data(php_style_input, segment_size):
segment = pkcs1._pad_for_signing(chunk, key_length)
try:
num = transform.bytes2int(segment)
encrypted = core.encrypt_int(num, key.d, key.n)
encrypted_bytes = transform.int2bytes(encrypted)
pad_length = key_length - len(encrypted_bytes)
first_block = b'\x00' * pad_length + encrypted_bytes
encrypted_base_64 += first_block
except Exception as e:
print(f"encrypt failed: {e}")
break
data = encrypted_base_64.hex()
print(f"v3 final result: {data}")
//decrypt
import base64
import binascii
import rsa
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from textwrap import wrap
from rsa import pkcs1, core, transform, PrivateKey, PublicKey, sign, verify, VerificationError
def _chunk_data(data, chunk_size):
"""Yield successive chunks from the input data."""
for i in range(0, len(data), chunk_size):
yield data[i:i + chunk_size]
public_key_string = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjHk1MSNR5yEBilpX41JIS9a/jB9GeL7IhLv3ycGh4drOtz+s7h0l3+dCuM+qI2zLY/sxw9I3J91BPA424mWV0uoyiXpEP8vhk0PS+UoYGsqbPwlLjfzf0e0VkI1eg4muZ89l8jKODsiYEsu76WmSZp3D3ezyZAbp6rfSly9HCNwIDAQAB"
public_key_pem = "-----BEGIN PUBLIC KEY-----\n" + "\n".join(wrap(public_key_string, 64)) + "\n-----END PUBLIC KEY-----"
print(public_key_pem)
try:
key = RSA.importKey(public_key_pem)
except ValueError as e:
print(f"load public key: {e}")
exit()
segment_size = key.n.bit_length() // 8
encrypted_base_64 = b''
# print(dir(key))
plain_data = 'd235790f2b14ccb0f4e068367147c7ac694ae3216a208dc45ae5dfbb12547dedc18abbb9c117bf6f005b10f7c8234fd91746f17d17f3c64b905049540d841f5319589b1c80e3f09a303e03655c4f46bfcff40b4d0a03f8d2120b6278713c02c8b69c3318e6fd66988dccd82435259a7826e7b30340c7907350981c5b1b1dea86'
decrypt_text_unhex = binascii.unhexlify(plain_data)
# for chunk in _chunk_data(php_style_input, segment_size):
decrypted_data = b''
for chunk in _chunk_data(decrypt_text_unhex, segment_size):
num = transform.bytes2int(chunk)
decrypted_int = core.decrypt_int(num, key.e, key.n)
decrypted_data += transform.int2bytes(decrypted_int)
# decrypted_data += pkcs1.decrypt(chunk, key)
data = base64.b64decode(decrypted_data).decode('utf-8')
print(f"v3 final result: {data}")
8. Nodejs Encryption/Decryption code:
const crypto = require("node:crypto");
// 文档里给出的商户 RSA 私钥,格式是去掉 PEM 头尾和换行后的 Base64 字符串。
// Node crypto 使用 RSA 密钥时需要 PEM 格式,所以后面会通过 base64KeyToPem 还原。
const PRIVATE_KEY =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKwiNo11h/VKCYedT0VdxxalCwhfCt79oGO5FxUmZiYUoiXAOHGrQWLElKZ2ezmwna5or0IFkpbhedtQ7SrrGtc/S9An2eKehoMVXU3tVEE27rVnOPC4FqEomZ5h1VJTeGALxlaKnF5lI65pVQ3q3V2YsMx4vmSWKNaf9HTuo9bzAgMBAAECgYAD+KYZjWSdnB+sKUzy5L77HsOqZcbybheNNW/65O/mYQN8q3qh5LmVdcOYM5OUOSbqJzAj7cz7/ie5j5xpKRNtahw9vJsl321PxjiQDsai6W2TwQdNestrbX2o9FEMa59OwUfzAf4lynJ8xjdA2B/3QI08JvqaAZ6MndNCBEoIQQJBAO62gtchvVirikbUU2D4un+hmdU9Uj0g+gFBRGnsTmFCBiL9v0aqiD76J0Aa8gVKXZXvOyBxbKLLVLfLNGUlLt8CQQC4mWQyxUVYR3eIC01I2pAvxMfT1fIao1RgDMNI10g8FBYVs4wZwnc7yX2PfKNzqMcIxNW1+NKqINqQI9xhh15tAkAhOAS9K1TOIhD8ClAQDozldfeSVRY8q3oe8pYyp0/A+Q8hj24ux0xudyE/KoDDe7XKR6BSw3X6sZD4gq6n5KTBAkAS8fsskr5pLvx/g9lsrrG5lVKE1SJBxZ11NhocsauCLvWNSJ4KTsD569XtEfeceSfkKH9ea6kDONf1jxihEcmJAkEAjbn2HzUtg+r2dkadYJ5k7hVmcNeVlrC8GpclEPJh3OBbc8VxCcIzWvH/LRm3o2lMcdTfj4jjQSMxwRilPydRWg==";
// 文档里给出的平台 RSA 公钥,用于解密平台返回的 result。
// 注意:这里的返回解密是“公钥解密”,是为了匹配文档里的 Java/PHP/Python 示例逻辑。
const PLATFORM_PUBLIC_KEY =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjHk1MSNR5yEBilpX41JIS9a/jB9GeL7IhLv3ycGh4drOtz+s7h0l3+dCuM+qI2zLY/sxw9I3J91BPA424mWV0uoyiXpEP8vhk0PS+UoYGsqbPwlLjfzf0e0VkI1eg4muZ89l8jKODsiYEsu76WmSZp3D3ezyZAbp6rfSly9HCNwIDAQAB";
// 以下三项都是文档里的示例数据:
// 1. SAMPLE_USER_NO:商户/用户编号,请求体里的 userNo。
// 2. SAMPLE_DATA:业务参数,会被 JSON -> Base64 -> RSA 加密。
// 3. SAMPLE_RESPONSE_BODY:接口返回示例,其中 result 是平台加密后的 hex 字符串。
const SAMPLE_USER_NO = "2024493320640815396";
const SAMPLE_DATA = { userReqNo: SAMPLE_USER_NO };
const SAMPLE_RESPONSE_BODY = {
result:
"d235790f2b14ccb0f4e068367147c7ac694ae3216a208dc45ae5dfbb12547dedc18abbb9c117bf6f005b10f7c8234fd91746f17d17f3c64b905049540d841f5319589b1c80e3f09a303e03655c4f46bfcff40b4d0a03f8d2120b6278713c02c8b69c3318e6fd66988dccd82435259a7826e7b30340c7907350981c5b1b1dea86",
async: false,
success: true,
userNo: "2024606286272130274",
};
function base64KeyToPem(type, key) {
// 文档里的密钥是一整行 Base64。PEM 标准通常每 64 个字符换行,
// 并包上 BEGIN/END 头尾,Node crypto 才能正确识别密钥类型。
const normalizedType = type.toUpperCase();
const wrappedKey = key.match(/.{1,64}/g).join("\n");
return `-----BEGIN ${normalizedType} KEY-----\n${wrappedKey}\n-----END ${normalizedType} KEY-----`;
}
function hexToBuffer(hex) {
// 接口里的 dataContent/result 都是二进制加密结果转成的 hex 字符串。
// 这里先做基本校验,避免传入非 hex 字符串时 crypto 抛出不易理解的错误。
if (typeof hex !== "string" || hex.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex)) {
throw new Error("hex must be a valid even-length hexadecimal string");
}
return Buffer.from(hex, "hex");
}
function bufferToHex(buffer) {
// Buffer.toString("hex") 会把每个字节转为两个十六进制字符,
// 这和文档中 dataContent/result 的格式一致。
return Buffer.from(buffer).toString("hex");
}
function getRsaKeyBytes(pem) {
// 文档使用 1024 位 RSA 密钥,所以 modulusLength 是 1024 bit。
// RSA 每个加密块的输出长度固定为密钥长度:1024 / 8 = 128 bytes。
const keyObject = crypto.createPrivateKey(pem);
return Math.ceil(keyObject.asymmetricKeyDetails.modulusLength / 8);
}
function chunkBuffer(buffer, chunkSize) {
// RSA 不能一次加密任意长度的数据,需要按照密钥长度和 padding 规则分段处理。
// 这里保持通用写法,后面加密和解密都会复用。
const chunks = [];
for (let offset = 0; offset < buffer.length; offset += chunkSize) {
chunks.push(buffer.subarray(offset, offset + chunkSize));
}
return chunks;
}
function rsaPrivateEncryptByChunks(dataBuffer, privateKeyPem) {
const keyBytes = getRsaKeyBytes(privateKeyPem);
// PKCS#1 v1.5 padding 固定占用 11 bytes。
// 对 1024 位 RSA 来说,每段最多加密 128 - 11 = 117 bytes 明文。
const maxChunkSize = keyBytes - 11;
// 文档要求使用商户私钥加密请求参数,padding 对应 Java 的 RSA/ECB/PKCS1Padding。
// Node 里没有 ECB 参数,RSA 本身不使用 ECB 分组模式;指定 RSA_PKCS1_PADDING 即可对齐。
const encryptedChunks = chunkBuffer(dataBuffer, maxChunkSize).map((chunk) =>
crypto.privateEncrypt(
{
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_PADDING,
},
chunk,
),
);
return Buffer.concat(encryptedChunks);
}
function rsaPublicDecryptByChunks(encryptedBuffer, publicKeyPem) {
const publicKey = crypto.createPublicKey(publicKeyPem);
const keyBytes = Math.ceil(publicKey.asymmetricKeyDetails.modulusLength / 8);
// RSA 每段密文长度固定等于密钥字节数。1024 位密钥下,每段密文必须是 128 bytes。
// 如果不是整数倍,说明 result/dataContent 被截断、拼接错误,或使用了不匹配的密钥。
if (encryptedBuffer.length % keyBytes !== 0) {
throw new Error(`encrypted data length must be a multiple of ${keyBytes} bytes`);
}
// 文档里的响应 result 是平台侧加密后的 hex,这里用平台公钥按块解密。
const decryptedChunks = chunkBuffer(encryptedBuffer, keyBytes).map((chunk) =>
crypto.publicDecrypt(
{
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_PADDING,
},
chunk,
),
);
return Buffer.concat(decryptedChunks);
}
function encryptRequestBody(data, userNo) {
const privateKeyPem = base64KeyToPem("PRIVATE", PRIVATE_KEY);
// 请求加密流程和文档保持一致:
// 业务对象 -> JSON 字符串 -> Base64 字符串 -> RSA 私钥加密 -> hex 字符串。
const jsonData = JSON.stringify(data);
const base64Data = Buffer.from(jsonData, "utf8").toString("base64");
const encryptedData = rsaPrivateEncryptByChunks(Buffer.from(base64Data, "utf8"), privateKeyPem);
// 最终请求体字段和文档一致。
// dataType 固定为 json,version 固定为 1.0.0,dataContent 是加密后的 hex。
return {
dataContent: bufferToHex(encryptedData),
dataType: "json",
userNo,
version: "1.0.0",
};
}
function decryptResponseResult(responseBody) {
const publicKeyPem = base64KeyToPem("PUBLIC", PLATFORM_PUBLIC_KEY);
// 响应解密流程和文档保持一致:
// result hex 字符串 -> Buffer -> RSA 公钥解密 -> Base64 字符串 -> 原始业务 JSON。
const encryptedBuffer = hexToBuffer(responseBody.result);
const decryptedBase64 = rsaPublicDecryptByChunks(encryptedBuffer, publicKeyPem)
.toString("utf8")
// 兼容部分语言/库在 Base64 字符串里插入换行的情况。
.replace(/\r|\n/g, "");
return Buffer.from(decryptedBase64, "base64").toString("utf8");
}
function runDemo() {
// 下面的输出字段命名尽量贴近文档示例,方便逐行对照 Java/PHP/Python demo。
console.log("rsa private key:", PRIVATE_KEY);
console.log("before encrypt request param:", JSON.stringify(SAMPLE_DATA));
const requestBody = encryptRequestBody(SAMPLE_DATA, SAMPLE_USER_NO);
console.log("encrypt data:", requestBody.dataContent);
console.log("request body:", JSON.stringify(requestBody));
console.log("rsa public key:", PLATFORM_PUBLIC_KEY);
console.log("response body:", JSON.stringify(SAMPLE_RESPONSE_BODY));
console.log("before decrypt response data:", SAMPLE_RESPONSE_BODY.result);
console.log("decrypt result:", decryptResponseResult(SAMPLE_RESPONSE_BODY));
}
if (require.main === module) {
runDemo();
}
module.exports = {
base64KeyToPem,
hexToBuffer,
bufferToHex,
encryptRequestBody,
decryptResponseResult,
};