跳到主要内容

嵌入代码

源代码是用 Java 编写的,用于原生 Android 开发。


第 1 步: 实施 ECC 库

.../android/appbuild.gradle 中实现 ECC 库。

android {
...
defaultConfig {
...
multiDexEnabled true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE.md'
exclude 'META-INF/NOTICE.md'
}
...
}

dependencies {
...
// Crypto
implementation 'com.nimbusds:nimbus-jose-jwt:8.9'
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
...
}

第 2 步: 声明输入

声明请求中的所有输入参数并创建 JSON 字符串。

销售请求参数
public void receiveMessage(ReadableMap data) {
String msgVer = data.getString("msgVer");
String pmtType = data.getString("pmtType");
String callerDevice = data.getString("callerDevice");
String callerDeviceVer = data.getString("callerDeviceVer");
String txnID = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
String txnDateTime = data.getString("txnDateTime");
String email = data.getString("email");
String password = data.getString("password");
String crcyCde = data.getString("crcyCde");
String amt = data.getString("amt");
String isOptInPrintReceipt = data.getString("isOptInPrintReceipt");
String isOptInSendEReceipt = data.getString("isOptInSendEReceipt");
String isOptInEReceipt = data.getString("isOptInEReceipt");
String isOptInPmtAckmnt = data.getString("isOptInPmtAckmnt");
String desc = data.getString("desc");

String jsonMessage = "{'TxnTyp' : '" + TestPos.TransactionType.STX +
"', 'MsgVer' : '" + msgVer +
"', 'PmtType' : '" + pmtType +
"', 'CallerDeviceType' : '" + callerDevice +
"', 'CallerDeviceVer' : '" + callerDeviceVer +
"', 'TxnID' : '" + txnID +
"', 'LocalTxnDTTime' : '" + txnDateTime +
"', 'Email' : '" + email +
"', 'Pwd' : '" + password +
"', 'AmtTxn' : '" + amt +
"', 'CrcyTxn' : '" + crcyCde +
"', 'OptInPrintReceipt' : '" + isOptInPrintReceipt +
"', 'OptInSendEReceipt' : '" + isOptInSendEReceipt +
"', 'OptInEReceipt' : '" + isOptInEReceipt +
"', 'OptInPmtAckmnt' : '" + isOptInPmtAckmnt +
"', 'Description' : '" + desc + "'}";

ECKey publicKeyASJWK = new ECKey.Builder(Curve.P_256, thirdAppPublicKey)
.keyID(UUID.randomUUID().toString())
.build();

String jsonMessageEncrypted = UtilCrypto.encryptWithFormatJWE(txnID,
publicKeyASJWK.toJSONString(), secretKey, JWEAlgorithm.DIR, jsonMessage);

startActivity(REQ_SALE, jsonMessageEncrypted);
}

第 3 步: 加密请求消息

在将请求消息发送到 Kayaaku POS 应用程序之前对其进行加密。

在 UtilCrypto.java 中
public static String encryptWithFormatJWE(String transactionID, String sdkPublicKeyAsJWK, SecretKey secretKey, JWEAlgorithm jweAlgorithm, String message) {
String encryptedMessage = "";

try {
//header
JWEHeader.Builder builder = new JWEHeader.Builder(jweAlgorithm, EncryptionMethod.A128CBC_HS256);
if (!TextUtils.isEmpty(transactionID)) {
builder.keyID(transactionID);
}
if (!TextUtils.isEmpty(sdkPublicKeyAsJWK)) {
builder.ephemeralPublicKey(ECKey.parse(sdkPublicKeyAsJWK));
}
JWEHeader header = builder.build();

//encrypt
if(jweAlgorithm == JWEAlgorithm.DIR) {
Provider bouncyCastleProvider = BouncyCastleProviderSingleton.getInstance();
Security.addProvider((bouncyCastleProvider));
JWEObject jweObject = new JWEObject(header, new Payload(message));
jweObject.encrypt(new DirectEncrypter(secretKey));
encryptedMessage = jweObject.serialize();
} else if (jweAlgorithm == JWEAlgorithm.ECDH_ES) {
// build JWE structure
JWECryptoParts jweParts = ContentCryptoProvider.encrypt(header, message.getBytes(), secretKey, null, new JWEJCAContext());

// JWE Protected Header
StringBuilder stringBuilder = new StringBuilder(header.toBase64URL().toString());
stringBuilder.append('.');

// Encrypted Key
if (jweParts.getEncryptedKey() != null) {
stringBuilder.append(jweParts.getEncryptedKey().toString());
}
stringBuilder.append('.');

// Initialization Vector
if (jweParts.getInitializationVector() != null) {
stringBuilder.append(jweParts.getInitializationVector().toString());
}
stringBuilder.append('.');

// Ciphertext
stringBuilder.append(jweParts.getCipherText().toString());
stringBuilder.append('.');

// Authentication Tag
if (jweParts.getAuthenticationTag() != null) {
stringBuilder.append(jweParts.getAuthenticationTag().toString());
}
encryptedMessage = stringBuilder.toString();
} else {
Log.d("Tag", "Unsupported other algorithms");
}
} catch (Exception ex) {
ex.printStackTrace();
}
return encryptedMessage;
}

第 4 步: 执行应用程序到应用程序的调用

商家可以选择使用INTENT蓝牙来执行应用程序到应用程序的通信。


第 4.1 步: 使用 INTENT 的应用到应用

要使用 INTENT,Kayaaku POS 应用程序和商家的 POS 应用程序必须安装在同一设备上。

按照下面的示例代码使用startActivityForResult()

INTENT
// start intent activity
private void startActivity (int messageType, String encryptedMessage) {
if (messageType == REQ_SALE) {
Intent intent = new Intent(ACTION_SALE); // ACTION_SALE = "com.finexuscards.yippiepos.xxx.SALES"
intent.putExtra("Data", encryptedMessage);
startActivityForResult(intent, REQ_SALE); // REQ_SALE = 0
} else if (messageType == REQ_VOID) {
Intent intent = new Intent(ACTION_VOID); // ACTION_VOID = "com.finexuscards.yippiepos.xxx.VOID"
intent.putExtra("Data", encryptedMessage);
startActivityForResult(intent, REQ_VOID); // REQ_VOID = 1
} else if (messageType == REQ_REFUND) {
Intent intent = new Intent(ACTION_REFUND); // ACTION_VOID = "com.finexuscards.yippiepos.xxx.REFUND"
intent.putExtra("Data", encryptedMessage);
startActivityForResult(intent, REQ_REFUND); // REQ_REFUND = 2
}
}

// get response from onActivityResult()
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
String encryptedResponse = data.getStringExtra("Data");
}

第 4.2 步: 使用蓝牙的应用程序到应用程序

按照下面的示例代码使用startActivityForResult()

Bluetooth
Import android.bluetooth.BluetoothSocket;
private BluetoothSocket mBluetoothSocket;

// send request via bluetooth
public void sendMessage(String message) {
OutputStream outputStream = mBluetoothSocket.getOutputStream();
outputStream.write((encryptedMessage + “\n”).getBytes());
}

// receive response via Bluetooth
private void startListener() {
InputStream inputStream = mBluetoothSocket.getInputStream();
bytes[] buffer = new byte[1024];

int bytes;
bytes = inputStream.read(buffer);
String encryptedResponse = new String(buffer, 0, bytes);
}

第 5 步: 私钥和公钥

第 5.1 步: 生成密钥

生成公钥和私钥。

import java.io.FileOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;

public void generateECCKeys(String outputPath) {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
pg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
KeyPair keyPair = kpg.generateKeyPair();
saveKeyPair(outputPath, keyPair);
} catch (Exception ex) {
ex.printStackTrace();
}
}

public void saveKeyPair(String outputPath, KeyPair keyPair) {
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
try {

StringBuilder content = new StringBuilder();
FileOutputStream fos = new FileOutputStream(outputPath + "./public.key");
content.append(Base64.getEncoder().encodeToString(publicKey.getEncoded()));
content.insert(0, "-----BEGIN PUBLIC KEY-----\n");
content.insert(content.length(), "\n-----END PUBLIC KEY-----");
fos.write(content.toString().getBytes());
fos.close();
content.setLength(0);
fos = new FileOutputStream(outputPath + "./private.key");
content.append(Base64.getEncoder().encodeToString(privateKey.getEncoded()));
content.insert(0, "-----BEGIN PRIVATE KEY-----\n");
content.insert(content.length(), "\n-----END PRIVATE KEY-----");
fos.write(content.toString().getBytes());
fos.close();

} catch (Exception ex) {
ex.printStackTrace();
}
}


第 5.2 步: 初始化按键

初始化将用于加密和解密过程的密钥。

TestPos(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
try {
thirdAppPrivateKey = UtilCrypto.loadECPrivateKey(reactContext.getAssets().open("thirdapp_private_key.key"));
thirdAppPublicKey = UtilCrypto.loadECPublicKey(reactContext.getAssets().open("thirdapp_public_key.key"));
yippiePosPublicKey = UtilCrypto.loadECPublicKey(reactContext.getAssets().open("yippiepos_public_key.key"));
String refNum = "1.0";
secretKey = UtilCrypto.generateECDHSecret(thirdAppPrivateKey, yippiePosPublicKey, refNum);
} catch (IOException ex) {
ex.printStackTrace();
}

BA = BluetoothAdapter.getDefaultAdapter();
}

第 6 步:创建类

创建一个名为UtilCrypto的新 Java 类,用于加密和解密功能。


第 7 步: 加载初始化密钥

从密钥文件加载初始化的密钥。

对于 UtilCrypto.java 中的私钥
public static ECPrivateKey loadECPrivateKey(InputStream certFile) {
ECPrivateKey ecPrivateKey = null;
PrivateKey privateKey = null;
try {
byte[] encodedPrivateKey = new byte[certFile.available()];
certFile.read(encodedPrivateKey);
certFile.close();
try {
String certFileContent = new String(encodedPrivateKey)
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----","")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
encodedPrivateKey = android.util.Base64.decode(certFileContent, android.util.Base64.DEFAULT);
} catch (Exception ex) {
ex.printStackTrace();
}
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
e.printStackTrace();
}
if (privateKey instanceof ECPrivateKey){
ecPrivateKey = (ECPrivateKey) privateKey;
}
return ecPrivateKey;
}

第 8 步: 解密响应消息

使用decryptWithFormatJWE函数解密从 Kayaaku POS 应用程序检索到的响应消息。

在 UtilCrypto.java 中
public static String decryptWithFormatJWE(SecretKey secretKey, String message) {
String decryptedMessage = "";
try {
// parse message to Object
JWEObject jweObject = JWEObject.parse(message);

// get header
JWEAlgorithm algorithm = jweObject.getHeader().getAlgorithm();
EncryptionMethod encryptionMethod = jweObject.getHeader().getEncryptionMethod();

// decrypt
if (algorithm.equals(JWEAlgorithm.DIR)) {
if (encryptionMethod == EncryptionMethod.A128CBC_HS256 || encryptionMethod == EncryptionMethod.A128GCM) {
Provider bouncyCastleProvider = BouncyCastleProviderSingleton.getInstance();
Security.addProvider(bouncyCastleProvider);

// start decrypting
jweObject.decrypt(new DirectDecrypter(secretKey));
decryptedMessage = jweObject.getPayload().toString();
} else {
Log.d("Tag", "Unsupported other encryption method:" + encryptionMethod);
}
} else {
Log.d("Tag", "Unsupported other algorithms");
}
} catch (Exception ex) {
Log.e("Tag", "decryptedMessage Failed:" + ex.getMessage());
ex.printStackTrace();
}
return decryptedMessage;
}