API Call Description

I. Interface Request:

Interface parameters

Sequence NumberDomain NameVariable NameRequiredTypeRemarks
1Version NumberversionMString(6)1.0.0
2Merchant NumberuserNoMNumberUnique number provided to merchants
3TypedataTypeMString(8)Business Type
4DatagramdataContentMString(2048)Data Content
  1. Convert dataContent to a JSON string
  2. Base64 encode a JSON string
  3. Use RSA 1024 to asymmetrically encrypt the Base64-encoded string into a byte array
  4. Convert a byte array to a hexadecimal string

2. Interface Return:

Sequence NumberDomain NameVariable NameRequiredTypeRemarks
1Success IdentifiersuccessMbooleanReturn true (success) or false
2Error CodeerrorCodeOString(6)success is false, errorCode has a value. For details, see the error code description.
3Error DescriptionerrorMsgOString(256)Error description, not empty when success is false
4Return result informationresultOString(1024)SUCCESS is empty when it is false, and not empty otherwise
5Merchant NumberuserNoMString(11)Unique number provided to merchants
6Is it asynchronousasyncMBooleanTrue 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,
};