嵌入代码
- Java
- PHP
- Node.js
- .Net
- Python
步骤 1 : 导入
导入用于发送 HTTP 请求的库。
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.crypto.impl.ECDH;
import com.nimbusds.jose.util.ByteUtils;
import com.nimbusds.jose.util.IntegerUtils;
步骤 2 : 生成密钥
使用EC私钥和Finexus POS公钥生成密钥以加密请求体中的数据。请参考 附录 4 生成 EC 密钥对。
public void run() throws Exception {
try {
ECPrivateKey privateKey2 = getECPrivateKeyFromPEM("path/to/ec_private_key_pkcs8.pem");
ECPublicKey fnxPublicKey = getECPublicKeyFromPEM("path/to/finexus_pos_public_key.key");
SecretKey encryptSecretKey = ECDH.deriveSharedSecret(fnxPublicKey, privateKey2, null);
int keyDataLen = 512;
String partyVInfo = "1.0";
SecretKey derivedKey = getSecretKey(
encryptSecretKey,
keyDataLen,
partyVInfo.getBytes()
);
byte[] keyBytes = derivedKey.getEncoded();
String base64Key = Base64.getEncoder().encodeToString(keyBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public static ECPrivateKey getECPrivateKeyFromPEM(String pemFilePath) throws Exception {
String pem = new String(Files.readAllBytes(Paths.get(pemFilePath)));
pem = pem.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", ""); // Remove line breaks and spaces
byte[] encoded = Base64.getDecoder().decode(pem);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return (ECPrivateKey) keyFactory.generatePrivate(keySpec);
}
public static ECPublicKey getECPublicKeyFromPEM(String pemFilePath) throws Exception {
String pem = new String(Files.readAllBytes(Paths.get(pemFilePath)));
pem = pem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", ""); // Remove line breaks and spaces
byte[] encoded = Base64.getDecoder().decode(pem);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return (ECPublicKey) keyFactory.generatePublic(keySpec);
}
public SecretKey getSecretKey(final SecretKey sharedSecret,
final int keyLength,
final byte[] partyVInfo)
throws JOSEException, IOException, NoSuchAlgorithmException, InvalidKeyException {
SecretKey derivedKey = deriveKey(
sharedSecret,
keyLength,
encodeDataWithLength(new byte[0]),
encodeDataWithLength(new byte[0]),
encodeDataWithLength(partyVInfo),
encodeIntData(keyLength),
encodeNoData());
return derivedKey;
}
public String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public SecretKey deriveKey(final SecretKey sharedSecret,
final int keyLength,
final byte[] algID,
final byte[] partyUInfo,
final byte[] partyVInfo,
final byte[] suppPubInfo,
final byte[] suppPrivInfo)
throws JOSEException, IOException, NoSuchAlgorithmException, InvalidKeyException {
final byte[] otherInfo = composeOtherInfo(algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
return deriveKeyOtherInfo(sharedSecret, keyLength, otherInfo);
}
public SecretKey deriveKeyOtherInfo(final SecretKey sharedSecret,
final int keyLengthBits,
final byte[] otherInfo)
throws JOSEException, IOException, NoSuchAlgorithmException, InvalidKeyException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new JOSEException("Algorithm not found: SHA-256", e);
}
for (int i = 1; i <= computeDigestCycles(ByteUtils.safeBitLength(md.getDigestLength()), keyLengthBits); i++) {
byte[] counterBytes = IntegerUtils.toBytes(i);
md.update(counterBytes);
md.update(sharedSecret.getEncoded());
if (otherInfo != null) {
md.update(otherInfo);
}
try {
baos.write(md.digest());
} catch (IOException e) {
throw new JOSEException("Couldn't write derived key: " + e.getMessage(), e);
}
}
byte[] derivedKeyMaterial = baos.toByteArray();
final int keyLengthBytes = ByteUtils.byteLength(keyLengthBits);
if (derivedKeyMaterial.length == keyLengthBytes) {
return new SecretKeySpec(derivedKeyMaterial, "AES");
}
return new SecretKeySpec(ByteUtils.subArray(derivedKeyMaterial, 0, keyLengthBytes), "AES");
}
public int computeDigestCycles(final int digestLengthBits, final int keyLengthBits) {
return (keyLengthBits + digestLengthBits - 1) / digestLengthBits;
}
public byte[] composeOtherInfo(final byte[] algID, final byte[] partyUInfo, final byte[] partyVInfo, final byte[] suppPubInfo, final byte[] suppPrivInfo) {
return ByteUtils.concat(algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
}
public byte[] encodeDataWithLength(final byte[] data) {
byte[] bytes = data != null ? data : new byte[0];
byte[] length = IntegerUtils.toBytes(bytes.length);
return ByteUtils.concat(length, bytes);
}
public static byte[] encodeIntData(final int data) {
return IntegerUtils.toBytes(data);
}
public static byte[] encodeNoData() {
return new byte[0];
}
步骤 3 : 源代码
构造 HTTP 请求正文。
ECPrivateKey thirdAppPrivateKey = UtilCrypto.getECPrivateKeyFromPEM(KEYPAIR_DIR + "ec_private_key_pkcs8.pem");
ECPublicKey thirdAppPublicKey = UtilCrypto.getECPublicKeyFromPEM(KEYPAIR_DIR + "ec_public_key.pem");
ECPublicKey yippiePosPublicKey = UtilCrypto.getECPublicKeyFromPEM(KEYPAIR_DIR + "yippiepos_public_key.key");
SecretKey encryptSecretKey = UtilCrypto.generateECDHSecret(thirdAppPrivateKey, yippiePosPublicKey, "1.0");
String requestJson = "{"
+ "'msgVer': '1.0',"
+ "'billerType': 'PS',"
+ "'callerDeviceType': '02',"
+ "'callerDeviceVer': '1',"
+ "'sequenceNo': '2022091511262185',"
+ "'sellerId': 'A123',"
+ "'mid': '000010000012XXX',"
+ "'isPaymentReq': 'Y',"
+ "'payment': {"
+ " 'mrn': '2021061414062021078',"
+ " 'paymentMethod': '01',"
+ " 'currency': '458',"
+ " 'amount': '1000',"
+ " 'description': 'test',"
+ " 'paymentTime': '2024-09-23T10:30:29.249',"
+ " 'email': 'customer123@gmail.com',"
+ " 'deviceSN': 'PPXXX72209005XXX',"
+ " 'printPaymentReceipt': 'N',"
+ " 'nonBlocking': 'N'"
+ "},"
+ "'isInvoiceReq': 'N'"
+ "}";
ECKey publicKeyAsJWK = new ECKey.Builder(Curve.P_256, thirdAppPublicKey)
.keyID(UUID.randomUUID().toString())
.build();
JWEHeader.Builder builder = new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256CBC_HS512);
builder.keyID("123");
builder.ephemeralPublicKey((JWK) ECKey.parse(publicKeyAsJWK.toJSONString()));
JWEHeader header = builder.build();
JWEObject jweObject = new JWEObject(header, new Payload(requestJson));
DirectEncrypter encrypter = new DirectEncrypter(encryptSecretKey);
jweObject.encrypt(encrypter);
步骤 4 : 生成签名
请参阅 附录1了解如何生成相应语言的签名。
步骤 5 : 将数据作为 POST 请求发布
在 生成签名 步骤中,使用创建的 signatureStr
作为 POST 请求的请求属性。
String mti = "001X";
String jweString = jweObject.serialize();
String requestBody = mti + jweString;
URL url = new URL("https://xxx.finexusgroup.com/fnx-fintech/xxx/mqpos-host/api/submit");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Signature", signatureStr);
connection.setRequestProperty("sourceSystem", "mxs");
connection.setRequestProperty("Content-Type", "text/plain");
connection.setDoOutput(true);
OutputStream os = connection.getOutputStream();
byte[] input = requestBody.getBytes("utf-8");
os.write(input, 0, input.length);
os.close();
int responseCode = connection.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = in.readLine()) != null) {
response.append(responseLine.trim());
}
in.close();
步骤 6 : 解密数据
要查看加密数据,请按如下方式解密:
JWEObject jweResObject = JWEObject.parse(responseLine.substring(4));
ECPublicKey recipientPublicKey = jweResObject.getHeader().getEphemeralPublicKey().toECKey().toECPublicKey();
SecretKey secrectKey = generateECDHSecret(thirdAppPrivateKey, recipientPublicKey, "1.0");
jweResObject.decrypt(new DirectDecrypter(secrectKey));
String decryptedRes = jweResObject.getPayload().toString();
步骤 1 : 导入
导入用于发送 HTTP 请求的库。
require 'vendor/autoload.php';
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\Serializer\CompactSerializer;
use Jose\Component\Encryption\Algorithm\KeyEncryption\Dir;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\Compression\Deflate;
use Jose\Component\Encryption\Serializer\JWESerializerManager;
use Jose\Component\Encryption\JWEDecrypter;
步骤 2 : 生成密钥
使用EC私钥和Finexus POS公钥生成密钥以加密请求体中的数据。请参考 附录 4 生成 EC 密钥对。
function encodeDataWithLength($data)
{
$bytes = $data !== null ? $data : [];
$length = pack('N', count($bytes));
return $length . implode('', array_map('chr', $bytes));
}
function encodeNoData()
{
return '';
}
function encodeIntData($data)
{
return pack('N', $data);
}
function composeOtherInfo($algID, $partyUInfo, $partyVInfo, $suppPubInfo, $suppPrivInfo)
{
return $algID . $partyUInfo . $partyVInfo . $suppPubInfo . $suppPrivInfo;
}
function computeDigestCycles($digestLengthBits, $keyLengthBits)
{
return intdiv($keyLengthBits + $digestLengthBits - 1, $digestLengthBits);
}
function deriveKeyOtherInfo($sharedSecret, $keyLengthBits, $otherInfo = null)
{
$derivedKeyMaterial = '';
$digestLengthBits = 256;
$digestLengthBytes = $digestLengthBits / 8;
$cycles = computeDigestCycles($digestLengthBits, $keyLengthBits);
for ($i = 1; $i <= $cycles; $i++) {
$counterBytes = pack('N', $i);
$hashInput = $counterBytes . $sharedSecret;
if ($otherInfo !== null) {
$hashInput .= $otherInfo;
}
$digest = hash('sha256', $hashInput, true);
$derivedKeyMaterial .= $digest;
}
$keyLengthBytes = $keyLengthBits / 8;
if (strlen($derivedKeyMaterial) > $keyLengthBytes) {
$derivedKeyMaterial = substr($derivedKeyMaterial, 0, $keyLengthBytes);
}
return $derivedKeyMaterial;
}
function deriveKey($sharedSecret, $keyLength, $partyVInfo)
{
$empty = "";
$emptyBytes = unpack('C*', $empty);
$otherInfo = composeOtherInfo(encodeDataWithLength($emptyBytes), encodeDataWithLength($emptyBytes), encodeDataWithLength($partyVInfo), encodeIntData($keyLength), encodeNoData());
return deriveKeyOtherInfo($sharedSecret, $keyLength, $otherInfo);
}
$ec_private_key_pkcs8 =
openssl_pkey_get_private(file_get_contents('ec_private_key_pkcs8.pem'));
$finexus_pos_public_key =
openssl_pkey_get_public(file_get_contents('finexus_pos_public_key.key'));
$sharedSecret = openssl_pkey_derive($finexus_pos_public_key, $ec_private_key_pkcs8);
$keyDataLen = 512;
$partyVInfo = "1.0";
$partyVInfoBytes = unpack('C*', $partyVInfo);
$derivedKey = deriveKey($sharedSecret, $keyDataLen, $partyVInfoBytes);
$secret = base64_encode($derivedKey);
步骤 3 : 源代码
构造 HTTP 请求正文。
function base64UrlEncodeSecret($base64) {
$decodedData = base64_decode($base64);
$urlSafeBase64 = base64_encode($decodedData);
$urlSafeBase64 = strtr($urlSafeBase64, '+/', '-_');
return rtrim($urlSafeBase64, '=');
}
function base64UrlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function generateUUID() {
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return sprintf(
'%08s-%04s-%04s-%04s-%12s',
bin2hex(substr($data, 0, 4)),
bin2hex(substr($data, 4, 2)),
bin2hex(substr($data, 6, 2)),
bin2hex(substr($data, 8, 2)),
bin2hex(substr($data, 10, 6))
);
}
// Generate JWE string
$payload = [
'msgVer' => '1.0',
'billerType' => 'PS',
'callerDeviceType' => '02',
'callerDeviceVer' => '1',
'sequenceNo' => '2024102411262215',
'sellerId' => 'A123',
'mid' => '000010000012XXX',
'isPaymentReq' => 'Y',
'payment' => [
'mrn' => '2024102411062021112',
'requiredPayment' => 'Y',
'paymentMethod' => '04',
'currency' => '458',
'amount' => '100',
'description' => 'test',
'paymentTime' => '2024-10-24T10:30:29.249',
'email' => 'customer123@gmail.com',
'deviceSN' => 'PPXXX72209005XXX',
'printPaymentReceipt' => 'N',
'nonBlocking' => 'N',
],
'isInvoiceReq' => 'N',
];
// Update with your file path
$filePath = 'ec_public_key.pem';
$pem = file_get_contents($filePath);
$publicKey = openssl_pkey_get_public($pem);
if (!$publicKey) {
throw new Exception("Failed to read public key");
}
$keyDetails = openssl_pkey_get_details($publicKey);
if ($keyDetails === false || !isset($keyDetails['key'])) {
throw new Exception("Failed to get public key details");
}
$publicKeyComponents = $keyDetails['ec'];
$x = base64UrlEncode($publicKeyComponents['x']);
$y = base64UrlEncode($publicKeyComponents['y']);
$epk = [
'kty' => 'EC', // Replace with actual key type
'crv' => 'P-256', // Replace with actual curve
'kid' => generateUUID(), // specific kid
'x' => $x, // Replace with actual x coordinate
'y' => $y, // Replace with actual y coordinate
];
$jweHeader = [
'epk' => $epk, // Example algorithm
'kid' => '123', // Example encryption algorithm
'enc' => 'A256CBC-HS512', // Example encryption algorithm
'alg' => 'dir', // Example algorithm
];
// Create the JWK (JSON Web Key)
$jwk = new JWK([
'kty' => 'oct',
'k' => base64UrlEncodeSecret($secret)
]);
$keyEncryptionAlgorithmManager = new AlgorithmManager([
new Dir(),
]);
$contentEncryptionAlgorithmManager = new AlgorithmManager([
new A256CBCHS512(),
]);
$compressionMethodManager = new CompressionMethodManager([
new Deflate(),
]);
$jweBuilder = new JWEBuilder(
$keyEncryptionAlgorithmManager,
$contentEncryptionAlgorithmManager,
$compressionMethodManager
);
$jwe = $jweBuilder
->create()
->withPayload(json_encode($payload))
->withSharedProtectedHeader($jweHeader)
->addRecipient($jwk)
->build();
$serializer = new CompactSerializer();
$jweCompact = $serializer->serialize($jwe);
步骤 4 : 生成签名
请参阅 附录1了解如何生成相应语言的签名。
步骤 5 : 将数据作为 POST 请求发布
在 生成签名 步骤中,在 POST 请求的标头中使用创建的 $signedMessage
,并在请求正文中使用 $data
。
$headers = [
"Content-Type: text/plain",
"Signature: $signedMessage",
"sourceSystem: mxs",
];
$url = "https://xxx.finexusgroup.com/fnx-fintech/sandbox/mqpos-host/api/submit";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_exec($ch);
curl_close($ch);
步骤 6 : 解密数据
要查看加密数据,请按如下方式解密:
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error_msg = curl_error($ch);
} else {
$str = substr($response, 4);
$decrypter = new JWEDecrypter($keyEncryptionAlgorithmManager, $contentEncryptionAlgorithmManager, $compressionMethodManager);
$serializer = new CompactSerializer();
$jwe = $serializer->unserialize($str);
if ($decrypter->decryptUsingKey($jwe, $jwk, 0)) {
$payload = $jwe->getPayload();
$originalPayload = json_decode($payload, true);
}
}
步骤 1 : 导入
导入用于发送 HTTP 请求的库。
const { KJUR } = require("jsrsasign");
const jose = require("jose");
const { v4: uuidv4 } = require("uuid");
const fs = require("fs");
const axios = require("axios");
const https = require("https");
const crypto = require('crypto');
const fsPromises = require("fs").promises;
步骤 2 : 生成密钥
使用EC私钥和Finexus POS公钥生成密钥以加密请求体中的数据。请参考 附录 4 生成 EC 密钥对。
function pemToArrayBuffer(pem) {
const base64 = pem
.replace(/-----BEGIN (?:.*) KEY-----|-----END (?:.*) KEY-----|\n/g, "")
.trim();
const binary = Buffer.from(base64, "base64");
return binary.buffer.slice(
binary.byteOffset,
binary.byteOffset + binary.byteLength
);
}
async function importPEMKey(filePath, isPrivateKey = false) {
const pem = await fsPromises.readFile(filePath, "utf8");
const keyBuffer = pemToArrayBuffer(pem);
const format = isPrivateKey ? "pkcs8" : "spki";
const algorithm = { name: "ECDH", namedCurve: "P-256" };
const usages = isPrivateKey ? ["deriveKey", "deriveBits"] : [];
return await crypto.subtle.importKey(
format,
keyBuffer,
algorithm,
true,
usages
);
}
async function deriveSharedSecret(privateKey, publicKey) {
return await crypto.subtle.deriveBits(
{
name: "ECDH",
public: publicKey,
},
privateKey,
256
);
}
function getSecretKey(sharedSecret, keyLength, partyVInfo) {
const derivedKey = deriveKey(
sharedSecret,
keyLength,
encodeDataWithLength(new Uint8Array(0)),
encodeDataWithLength(new Uint8Array(0)),
encodeDataWithLength(partyVInfo),
encodeIntData(keyLength),
encodeNoData()
);
return derivedKey;
}
function deriveKey(sharedSecret, keyLength, algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo) {
const otherInfo = composeOtherInfo(algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
return deriveKeyOtherInfo(sharedSecret, keyLength, otherInfo);
}
function deriveKeyOtherInfo(sharedSecret, keyLengthBits, otherInfo) {
const keyLengthBytes = Math.ceil(keyLengthBits / 8);
const hashRounds = Math.ceil(keyLengthBytes / 32);
const derivedKeyBuffer = Buffer.alloc(hashRounds * 32);
for (let i = 0; i < hashRounds; i++) {
const hash = crypto.createHash('sha256');
hash.update(Buffer.from([0, 0, 0, i + 1]));
hash.update(sharedSecret);
hash.update(otherInfo);
derivedKeyBuffer.set(hash.digest(), i * 32);
}
return derivedKeyBuffer.slice(0, keyLengthBytes);
}
function encodeDataWithLength(data) {
const bufferData = Buffer.from(data);
const lengthBuffer = Buffer.alloc(4);
lengthBuffer.writeUInt32BE(bufferData.length, 0);
return Buffer.concat([lengthBuffer, bufferData]);
}
function encodeIntData(data) {
const buffer = Buffer.alloc(4);
buffer.writeUInt32BE(data, 0);
return buffer;
}
function encodeNoData() {
return Buffer.alloc(0);
}
function composeOtherInfo(algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo) {
const buffers = [algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo];
return Buffer.concat(buffers);
}
const ec_private_key_pkcs8 = await importPEMKey(
"ec_private_key_pkcs8.pem",
true
);
const finexus_pos_public_key = await importPEMKey(
"finexus_pos_public_key.key",
false
);
const sharedSecretBits = await deriveSharedSecret(
ec_private_key_pkcs8,
finexus_pos_public_key
);
const sharedSecret = Buffer.from(sharedSecretBits, 'hex');
const keyDataLen = 512;
const partyVInfo = Buffer.from("1.0", 'utf8');
const derivedKey = getSecretKey(
sharedSecret,
keyDataLen,
partyVInfo
);
const secretKey = derivedKey.toString('base64');
步骤 3 : 源代码
构造 HTTP 请求正文。
async function getPublicKeyFromFile(filePath) {
const pem = fs.readFileSync(filePath, "utf8");
const publicKey = await jose.importSPKI(pem, "ES256");
const publicKeyJWK = await jose.exportJWK(publicKey);
return publicKeyJWK;
}
async function generateJWEObject() {
const publicKeyJWK = await getPublicKeyFromFile("ec_public_key.pem");
const epk = {
kty: publicKeyJWK.kty,
crv: publicKeyJWK.crv,
kid: uuidv4(),
x: publicKeyJWK.x,
y: publicKeyJWK.y,
};
const jweHeader = {
epk: epk,
kid: "123",
enc: "A256CBC-HS512",
alg: "dir",
};
const payload = {
msgVer: "1.0",
billerType: "PS",
callerDeviceType: "02",
callerDeviceVer: "1",
sequenceNo: "2024102411262211",
sellerId: "A123",
mid: "000010000012XXX",
isPaymentReq: "Y",
payment: {
mrn: "2024102411062021111",
requiredPayment: "Y",
paymentMethod: "04",
currency: "458",
amount: "100",
description: "test",
paymentTime: "2024-10-24T10:30:29.249",
email: "customer123@gmail.com",
deviceSN: "PPXXX72209005XXX",
printPaymentReceipt: "N",
nonBlocking: "N",
},
isInvoiceReq: "N",
};
const secret = jose.base64url.decode(secretKey);
const jwt = await new jose.EncryptJWT(payload)
.setProtectedHeader(jweHeader)
.encrypt(secret);
return jwt;
}
步骤 4 : 生成签名
请参阅 附录1了解如何生成相应语言的签名。
步骤 5 : 将数据作为 POST 请求发布
在 生成签名 步骤中,在 POST 请求的标头中使用创建的 signedMessage
,并在请求正文中使用 message
。
const headers = {
"Content-Type": "text/plain",
Signature: signedMessage,
sourceSystem: "mxs",
};
const httpsAgent = new https.Agent({
rejectUnauthorized: true,
});
const url = "https://xxx.finexusgroup.com/fnx-fintech/sandbox/mqpos-host/api/submit";
const response = await axios.post(url, message, { headers, httpsAgent });
步骤 6 : 解密数据
要查看加密数据,请按如下方式解密:
const str = response.data.slice(4);
const { payload, protectedHeader } = await jose.jwtDecrypt(str, secret);
步骤 1 : 导入
导入用于发送 HTTP 请求的库。
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
using Jose;
using Newtonsoft.Json;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;
步骤 2 : 生成密钥
使用EC私钥和Finexus POS公钥生成密钥以加密请求体中的数据。请参考 附录 4 生成 EC 密钥对。
public static byte[]? DeriveSharedSecret(string privateKeyPath, string publicKeyPath)
{
string sharedSecretPath = "shared_secret.bin";
string opensslCommand = $"openssl pkeyutl -derive -inkey \"{privateKeyPath}\" -peerkey \"{publicKeyPath}\" -out \"{sharedSecretPath}\"";
ProcessStartInfo processStartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {opensslCommand}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (Process? process = Process.Start(processStartInfo))
{
if (process != null)
{
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (process.ExitCode != 0)
{
return null;
}
byte[] sharedSecretBinValues = File.ReadAllBytes(sharedSecretPath);
return sharedSecretBinValues;
}
else
{
return null;
}
}
}
public static string GenerateSecretKey(byte[] sharedSecret, int keyLength, byte[] partyVInfo)
{
byte[] derivedKey = DeriveKey(
sharedSecret,
keyLength,
EncodeDataWithLength(new byte[0]),
EncodeDataWithLength(new byte[0]),
EncodeDataWithLengthReversed(partyVInfo),
EncodeIntData(keyLength),
EncodeNoData()
);
return Convert.ToBase64String(derivedKey);
}
public static byte[] DeriveKey(byte[] sharedSecret, int keyLength, byte[] algID, byte[] partyUInfo, byte[] partyVInfo, byte[] suppPubInfo, byte[] suppPrivInfo)
{
byte[] otherInfo = ComposeOtherInfo(algID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
return DeriveKeyOtherInfo(sharedSecret, keyLength, otherInfo);
}
public static byte[] ComposeOtherInfo(byte[] algID, byte[] partyUInfo, byte[] partyVInfo, byte[] suppPubInfo, byte[] suppPrivInfo)
{
return algID.Concat(partyUInfo)
.Concat(partyVInfo)
.Concat(suppPubInfo)
.Concat(suppPrivInfo)
.ToArray();
}
public static byte[] DeriveKeyOtherInfo(byte[] sharedSecret, int keyLengthBits, byte[] otherInfo)
{
int keyLengthBytes = (int)Math.Ceiling(keyLengthBits / 8.0);
int hashRounds = (int)Math.Ceiling(keyLengthBytes / 32.0);
byte[] derivedKeyBuffer = new byte[hashRounds * 32];
using (SHA256 sha256 = SHA256.Create())
{
for (int i = 0; i < hashRounds; i++)
{
byte[] counter = BitConverter.GetBytes(i + 1);
Array.Reverse(counter);
byte[] hashInput = new byte[counter.Length + sharedSecret.Length + otherInfo.Length];
Buffer.BlockCopy(counter, 0, hashInput, 0, counter.Length);
Buffer.BlockCopy(sharedSecret, 0, hashInput, counter.Length, sharedSecret.Length);
Buffer.BlockCopy(otherInfo, 0, hashInput, counter.Length + sharedSecret.Length, otherInfo.Length);
byte[] hash = sha256.ComputeHash(hashInput);
Buffer.BlockCopy(hash, 0, derivedKeyBuffer, i * 32, Math.Min(32, derivedKeyBuffer.Length - i * 32));
}
}
byte[] finalKey = new byte[keyLengthBytes];
Array.Copy(derivedKeyBuffer, finalKey, keyLengthBytes);
return finalKey;
}
public static byte[] EncodeDataWithLength(byte[] data)
{
byte[] bytes = data ?? new byte[0];
byte[] length = BitConverter.GetBytes(bytes.Length);
byte[] result = new byte[length.Length + bytes.Length];
Array.Copy(length, 0, result, 0, length.Length);
Array.Copy(bytes, 0, result, length.Length, bytes.Length);
return result;
}
public static byte[] EncodeDataWithLengthReversed(byte[] data)
{
byte[] bytes = data ?? new byte[0];
byte[] length = BitConverter.GetBytes(bytes.Length);
Array.Reverse(length);
byte[] result = new byte[length.Length + bytes.Length];
Array.Copy(length, 0, result, 0, length.Length);
Array.Copy(bytes, 0, result, length.Length, bytes.Length);
return result;
}
public static byte[] EncodeIntData(int data)
{
byte[] bytes = BitConverter.GetBytes(data);
Array.Reverse(bytes);
return bytes;
}
public static byte[] EncodeNoData()
{
return new byte[0];
}
var secretKey = "";
string privateKeyPath = "ec_private_key_pkcs8.pem";
string publicKeyPath = "finexus_pos_public_key.key";
byte[]? sharedSecretByte = DeriveSharedSecret(privateKeyPath, publicKeyPath);
if (sharedSecretByte != null)
{
int keyLength = 512;
string partyVInfo = "1.0";
byte[] partyVInfoBytes = System.Text.Encoding.UTF8.GetBytes(partyVInfo);
secretKey = GenerateSecretKey(sharedSecretByte, keyLength, partyVInfoBytes);
}
步骤 3 : 源代码
构造 HTTP 请求正文。
class Program
{
private static string GenerateJWEObject()
{
//path to public key
FileStream certFile = File.OpenRead("ec_public_key.pem");
byte[] thirdAppPublicKeyBytes = LoadECPublicKeyBytes(certFile);
var pubKeyCoordinates = GetPublicKeyCoordinates(thirdAppPublicKeyBytes);
dynamic coord = pubKeyCoordinates;
var epk = new JwtPayload
{
{"kty", "EC"},
{"crv", "P-256"},
{"kid", Guid.NewGuid().ToString()},
{"x", coord.X},
{"y", coord.Y}
};
var payload = new JwtPayload
{
{ "msgVer", "1.0" },
{ "billerType", "PS" },
{ "callerDeviceType", "02" },
{ "callerDeviceVer", "1" },
{ "sequenceNo", "2024102411262212" },
{ "sellerId", "A123" },
{ "mid", "000010000012XXX" },
{ "isPaymentReq", "Y" },
{ "payment", new Dictionary<string, object>
{
{ "mrn", "2024102411062021111" },
{ "requiredPayment", "Y" },
{ "paymentMethod", "04" },
{ "currency", "458" },
{ "amount", "100" },
{ "description", "test" },
{ "paymentTime", "2024-10-24T10:30:29.249" },
{ "email", "customer123@gmail.com" },
{ "deviceSN", "PPXXX72209005XXX" },
{ "printPaymentReceipt", "N" },
{ "nonBlocking", "N" }
}
},
{ "isInvoiceReq", "N" }
};
string jsonPayload = JsonConvert.SerializeObject(payload);
string jsonMessageEncrypted = EncryptWithFormatJWE(epk, secretKey, JweAlgorithm.DIR, jsonPayload);
return jsonMessageEncrypted;
}
private static byte[] LoadECPublicKeyBytes(Stream certFile)
{
using (MemoryStream memoryStream = new MemoryStream())
{
certFile.CopyTo(memoryStream);
byte[] publicKeyAsBytes = memoryStream.ToArray();
string certFileContent = Encoding.UTF8.GetString(publicKeyAsBytes)
.Replace("-----BEGIN CERTIFICATE-----", "")
.Replace("-----END CERTIFICATE-----", "")
.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", "")
.Replace("\n", "")
.Replace("\r", "");
publicKeyAsBytes = Convert.FromBase64String(certFileContent);
return publicKeyAsBytes;
}
}
private static object GetPublicKeyCoordinates(byte[] publicKeyBytes)
{
var asn1 = (Asn1Sequence)Asn1Object.FromByteArray(publicKeyBytes);
var at1 = (DerBitString)asn1[1];
var xyBytes = at1.GetBytes();
X9ECParameters x9 = ECNamedCurveTable.GetByName("P-256");
ECDomainParameters domainParams = new ECDomainParameters(x9.Curve, x9.G, x9.N, x9.H, x9.GetSeed());
ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(x9.Curve.DecodePoint(xyBytes), domainParams);
return new
{
X = EncodeCoordinate(publicKeyParams.Q.AffineXCoord.ToBigInteger()),
Y = EncodeCoordinate(publicKeyParams.Q.AffineYCoord.ToBigInteger())
};
}
private static string EncodeCoordinate(Org.BouncyCastle.Math.BigInteger integer)
{
var notPadded = integer.ToByteArray();
int bytesToOutput = (256 + 7) / 8;
if (notPadded.Length >= bytesToOutput)
return Jose.Base64Url.Encode(notPadded);
var padded = new byte[bytesToOutput];
Array.Copy(notPadded, 0, padded, bytesToOutput - notPadded.Length, notPadded.Length);
return Jose.Base64Url.Encode(padded);
}
private static string EncryptWithFormatJWE(JwtPayload epk, string secretKey, JweAlgorithm jweAlgorithm, string message)
{
string encryptedMessage = "";
try
{
string epkJson = epk.SerializeToJson();
var epkDictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(epkJson);
var jwtHeader = new JwtHeader
{
{"epk", epkDictionary },
{"kid", "123"},
};
byte[] secretKeyBytes = Convert.FromBase64String(secretKey);
encryptedMessage = JWT.Encode(message, secretKeyBytes, jweAlgorithm, JweEncryption.A256CBC_HS512, extraHeaders: jwtHeader);
}
catch (Exception ex)
{
}
return encryptedMessage;
}
}
步骤 4 : 生成签名
请参阅 附录1了解如何生成相应语言的签名。
步骤 5 : 将数据作为 POST 请求发布
在 生成签名 步骤中,在 POST 请求的标头中使用创建的 signedMessage
,并在请求正文中使用 message
。
private static async Task Main(string[] args)
{
await PostHttp();
}
private static async Task PostHttp()
{
var url = "https://xxx.finexusgroup.com/fnx-fintech/sandbox/mqpos-host/api/submit";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Signature", signedMessage);
client.DefaultRequestHeaders.Add("sourceSystem", "mxs");
var content = new StringContent(message, Encoding.UTF8, "text/plain");
HttpResponseMessage response = await client.PostAsync(url, content);
}
}
步骤 6 : 解密数据
要查看加密数据,请按如下方式解密:
string responseContent = await response.Content.ReadAsStringAsync();
string jwe = responseContent.Substring(4);
string decryptedMessage = "";
byte[] secretKeyBytes = Convert.FromBase64String(secretKey);
decryptedMessage = JWT.Decode(jwe, secretKeyBytes, JweAlgorithm.DIR, JweEncryption.A256CBC_HS512);
步骤 1 : 导入
导入用于发送 HTTP 请求的库。
import json
import uuid
from jwcrypto import jwk, jwe
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import (
load_pem_private_key,
load_pem_public_key,
)
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
import urllib.parse
import requests
步骤 2 : 生成密钥
使用EC私钥和Finexus POS公钥生成密钥以加密请求体中的数据。请参考 附录 4 生成 EC 密钥对。
def load_pem_key(pem_file_path):
with open(pem_file_path, "rb") as pem_file:
return pem_file.read()
def load_ec_private_key_from_pem(pem_file_path):
key_data = load_pem_key(pem_file_path)
private_key = load_pem_private_key(
key_data, password=None, backend=default_backend()
)
return private_key
def load_ec_public_key_from_pem(pem_file_path):
key_data = load_pem_key(pem_file_path)
public_key = load_pem_public_key(key_data, backend=default_backend())
return public_key
def derive_shared_secret(private_key, public_key):
shared_secret = private_key.exchange(ec.ECDH(), public_key)
return shared_secret
def encode_data_with_length(data):
length = len(data).to_bytes(4, byteorder="big")
return length + data
def encode_int_data(data):
return data.to_bytes(4, byteorder="big")
def encode_no_data():
return b""
def compose_other_info(alg_id, party_u_info, party_v_info, supp_pub_info, supp_priv_info):
return alg_id + party_u_info + party_v_info + supp_pub_info + supp_priv_info
def derive_key(shared_secret, key_length_bits, alg_id, party_u_info, party_v_info, supp_pub_info, supp_priv_info):
other_info = compose_other_info(
alg_id, party_u_info, party_v_info, supp_pub_info, supp_priv_info
)
concat_kdf = ConcatKDFHash(
algorithm=hashes.SHA256(),
length=(key_length_bits // 8),
otherinfo=other_info,
backend=default_backend(),
)
derived_key = concat_kdf.derive(shared_secret)
return derived_key
def get_secret_key(shared_secret, key_data_len, party_v_info):
alg_id = encode_data_with_length(b"")
party_u_info = encode_data_with_length(b"")
supp_pub_info = encode_int_data(key_data_len)
supp_priv_info = encode_no_data()
derived_key = derive_key(
shared_secret,
key_data_len,
alg_id,
party_u_info,
party_v_info,
supp_pub_info,
supp_priv_info,
)
return base64.b64encode(derived_key).decode()
private_key_loaded = load_ec_private_key_from_pem("ec_private_key_pkcs8.pem")
public_key_loaded = load_ec_public_key_from_pem("finexus_pos_public_key.key")
shared_secret = derive_shared_secret(private_key_loaded, public_key_loaded)
key_data_len = 512
party_v_info = encode_data_with_length(b"1.0")
secret_key = get_secret_key(shared_secret, key_data_len, party_v_info)
步骤 3 : 源代码
构造 HTTP 请求正文。
def generate_jwe_object():
# path to public key file
public_key_path = "ec_public_key.pem"
coordinates = load_ec_public_key_bytes(public_key_path)
if coordinates:
x_base64 = base64_url_encode(coordinates['x'])
y_base64 = base64_url_encode(coordinates['y'])
epk = {
"kty": "EC",
"crv": "P-256",
"kid": str(uuid.uuid4()),
"x": x_base64,
"y": y_base64,
}
jwe_header = {
"epk": epk,
"kid": "123",
"enc": "A256CBC-HS512",
"alg": "dir",
}
payload = {
"msgVer": "1.0",
"billerType": "PS",
"callerDeviceType": "02",
"callerDeviceVer": "1",
"sequenceNo": "2024102411262211",
"sellerId": "A123",
"mid": "000010000012XXX",
"isPaymentReq": "Y",
"payment": {
"mrn": "2024102411062021111",
"requiredPayment": "Y",
"paymentMethod": "04",
"currency": "458",
"amount": "100",
"description": "test",
"paymentTime": "2024-10-24T10:30:29.249",
"email": "customer123@gmail.com",
"deviceSN": "PPXXX72209005XXX",
"printPaymentReceipt": "N",
"nonBlocking": "N",
},
"isInvoiceReq": "N",
}
payload_json = json.dumps(payload)
protected_header_json = json.dumps(jwe_header)
secret_key_bytes = base64.urlsafe_b64decode(secret_key)
jwk_key = jwk.JWK(k=base64.urlsafe_b64encode(secret_key_bytes).decode("utf-8"), kty="oct")
jwe_obj = jwe.JWE(plaintext=payload_json.encode(), protected=protected_header_json)
jwe_obj.add_recipient(jwk_key)
encrypted_jwe = jwe_obj.serialize(compact=True)
return encrypted_jwe
def load_ec_public_key_bytes(cert_file_path):
try:
with open(cert_file_path, "rb") as cert_file:
public_key_bytes = cert_file.read()
public_key = serialization.load_pem_public_key(public_key_bytes)
if isinstance(public_key, ec.EllipticCurvePublicKey):
public_numbers = public_key.public_numbers()
x_coord = public_numbers.x
y_coord = public_numbers.y
return {"x": x_coord, "y": y_coord}
else:
return None
except FileNotFoundError:
return None
except Exception as e:
return None
def base64_url_encode(value):
value_bytes = value.to_bytes((value.bit_length() + 7) // 8, byteorder='big')
return base64.urlsafe_b64encode(value_bytes).decode('utf-8').rstrip("=")
步骤 4 : 生成签名
请参阅 附录1了解如何生成相应语言的签名。
步骤 5 : 将数据作为 POST 请求发布
在 生成签名 步骤中,在 POST 请求的标头中使用创建的 signed_message
,并在请求正文中使用 message
。
def post_http():
headers = {
"Content-Type": "text/plain",
"Signature": signed_message,
"sourceSystem": "mxs",
}
response = requests.post(
"https://xxx.finexusgroup.com/fnx-fintech/sandbox/mqpos-host/api/submit",
headers=headers,
data=message,
)
步骤 6 : 解密数据
要查看加密数据,请按如下方式解密:
response_content = response.text
jwe_token = response_content[4:]
secret_key_bytes = base64.urlsafe_b64decode(secret_key)
jwk_key = jwk.JWK(
k=base64.urlsafe_b64encode(secret_key_bytes).decode("utf-8"), kty="oct"
)
jwe_obj = jwe.JWE()
jwe_obj.deserialize(jwe_token)
jwe_obj.decrypt(jwk_key)
decrypted_message = jwe_obj.payload.decode("utf-8")