跳到主要内容

嵌入代码

步骤 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();