跳到主要内容

附录 1

安全控制​

商户和 PayMaster 共享一个 密钥,双方使用该密钥为交易数据创建安全哈希值。

  • 密钥应从 PayMaster 网关获取。

安全哈希是对消息头与数据内容中所有字段(不含尾部字段)拼接后再附加密钥,使用 SHA-256 HMAC(或 MD5 HMAC)进行十六进制编码后的结果。


请求页面 t002_SHV​

调用 PayMaster 之前,商户必须为请求消息 生成 t002_SHV,并将 t001_SHT 与 t002_SHV 作为尾部字段附加到请求中。

步骤 1:构建请求字符串(不含尾部)​

按字段 ID 升序拼接消息头与数据字段。每个 name=value 对之间用 & 分隔。不要包含 t001_SHT 或 t002_SHV。

DuitNow Pay 付款请求示例(0880):

不含尾部字段的请求消息
h001_MTI=0880&h002_VNO=06&h003_TDT=20260824&h004_TTM=12432905&f001_MID=120567801561128&f003_ProcCode=000000&f006_TxnDtTm=20010831124329&f007_TxnAmt=000000057080&f010_CurrCode=458&f019_ExpTxnAmt=2&f249_TxnCh=WEB&f253_CntyCode=MY&f256_FICode=RPP&f260_ServID=FNX&f263_MRN=2012121912345&f264_Locale=en&f268_CHName=Thomson&f275_RURL_DD=https://www.megastore.com/dd/done.jsp&f278_EMailAddr=thomson8899@yahoo.com&f279_HP=60128888888&f285_IPAddr=11.11.88.88&f352_AcqBank=FNXSMYNB

步骤 2:附加密钥​

将密钥附加到步骤 1 的字符串末尾:

不含尾部字段的请求消息 + 密钥

步骤 3:生成哈希值​

使用 t001_SHT 中指定的算法对字符串进行编码。本示例使用 SHA-256 HMAC(SH2)。将结果设为 t002_SHV,再将尾部字段附加到请求中。

在这个例子中:

  • 假设密钥为 00112233445566778899AABBCCDDEEFF,则哈希值为 6C825DC0E39F2917A8BC34A45F5BAF6EDDF7353446C19D3CC6296E6F3A962892。

最终请求尾部:

t001_SHT=SH2&t002_SHV=6C825DC0E39F2917A8BC34A45F5BAF6EDDF7353446C19D3CC6296E6F3A962892

示例代码(生成)​

public void reqPayment() throws Exception {

String secretKey = "00112233445566778899AABBCCDDEEFF";

String request =
"h001_MTI=0880&h002_VNO=06&h003_TDT=20260824&h004_TTM=12432905"
+ "&f001_MID=120567801561128&f003_ProcCode=000000"
+ "&f006_TxnDtTm=20010831124329&f007_TxnAmt=000000057080"
+ "&f010_CurrCode=458&f019_ExpTxnAmt=2&f249_TxnCh=WEB"
+ "&f253_CntyCode=MY&f256_FICode=RPP&f260_ServID=FNX"
+ "&f263_MRN=2012121912345&f264_Locale=en&f268_CHName=Thomson"
+ "&f275_RURL_DD=https://www.megastore.com/dd/done.jsp"
+ "&f278_EMailAddr=thomson8899@yahoo.com&f279_HP=60128888888"
+ "&f285_IPAddr=11.11.88.88&f352_AcqBank=FNXSMYNB"
+ secretKey;

SecretKeySpec byteSecretKey = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
Mac sha256_MAC = Mac.getInstance("HmacSHA256");
sha256_MAC.init(byteSecretKey);

String hashed = this.bytesToHex(sha256_MAC.doFinal(request.getBytes())).toUpperCase();

System.out.println(hashed);
// 6C825DC0E39F2917A8BC34A45F5BAF6EDDF7353446C19D3CC6296E6F3A962892
}

public String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}

响应页面 t002_SHV​

支付响应消息将包括 t001_SHT 和 t002_SHV。商户应使用这两个值来验证 PayMaster 发送的响应消息,以确保数据未被篡改。

要验证这一点,商家应从响应消息中删除尾部字段(t001_SHT 和 t002_SHV),在剩余消息中附加商家的密钥,然后使用 SHA-256 HMAC 哈希算法对字符串进行编码。

获得编码结果后,将其与 t002_SHV 中的值进行比较。

  • 值必须相等。

例子​

步骤 1:删除尾部字段​

从 PayMaster 网关返回的响应消息中删除尾部字段。

DuitNow Pay 付款响应示例(0890):

删除尾部值之前
h001_MTI=0890&h002_VNO=06&h003_TDT=20260824&h004_TTM=12432905&f001_MID=120567801561128&f003_ProcCode=000000&f006_TxnDtTm=20120831124329&f007_TxnAmt=000000057080&f010_CurrCode=458&f019_ExpTxnAmt=2&f254_DDRespCode=00&f256_FICode=RPP&f257_PGRN=AJ789EG07S0012012022&f258_TxnStatDetCde=0000&f259_TxnStatMsg=OK&f260_ServID=FNX&f263_MRN=2012121912345&f277_DDRN=DD200108301234&f286_OrgDDRespCode=00&f407_PayerAccType=TBH&t001_SHT=SH2&t002_SHV=0978A771F4F3395B00455BFBD7ADF732A09DBCFF65DE773009940DAA113248FC
删除尾部值之后
h001_MTI=0890&h002_VNO=06&h003_TDT=20260824&h004_TTM=12432905&f001_MID=120567801561128&f003_ProcCode=000000&f006_TxnDtTm=20120831124329&f007_TxnAmt=000000057080&f010_CurrCode=458&f019_ExpTxnAmt=2&f254_DDRespCode=00&f256_FICode=RPP&f257_PGRN=AJ789EG07S0012012022&f258_TxnStatDetCde=0000&f259_TxnStatMsg=OK&f260_ServID=FNX&f263_MRN=2012121912345&f277_DDRN=DD200108301234&f286_OrgDDRespCode=00&f407_PayerAccType=TBH

步骤 2:将密钥附加到响应消息中​

删除尾部字段后,将密钥附加到响应消息中。这将产生一个格式为“不带尾部字段的响应消息 + 密钥”的字符串。使用 t001_SHT 中指定的算法对该字符串进行编码。在此示例中,使用 SHA-256。

步骤 3:将编码结果与哈希值进行比较​

将编码结果与 t002_SHV 中的哈希值进行比较。这两个值 必须 匹配。

在这个例子中:

  • 假设密钥为 00112233445566778899AABBCCDDEEFF,则哈希值为 0978A771F4F3395B00455BFBD7ADF732A09DBCFF65DE773009940DAA113248FC。

商户必须拒绝任何不匹配的结果并将其报告给 PayMaster。

示例代码(验证)​

public void resPayment() throws Exception {

String secretKey = "00112233445566778899AABBCCDDEEFF";

String response =
"h001_MTI=0890&h002_VNO=06&h003_TDT=20260824&h004_TTM=12432905"
+ "&f001_MID=120567801561128&f003_ProcCode=000000"
+ "&f006_TxnDtTm=20120831124329&f007_TxnAmt=000000057080"
+ "&f010_CurrCode=458&f019_ExpTxnAmt=2&f254_DDRespCode=00"
+ "&f256_FICode=RPP&f257_PGRN=AJ789EG07S0012012022"
+ "&f258_TxnStatDetCde=0000&f259_TxnStatMsg=OK&f260_ServID=FNX"
+ "&f263_MRN=2012121912345&f277_DDRN=DD200108301234"
+ "&f286_OrgDDRespCode=00&f407_PayerAccType=TBH"
+ secretKey;

SecretKeySpec byteSecretKey = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
Mac sha256_MAC = Mac.getInstance("HmacSHA256");
sha256_MAC.init(byteSecretKey);

String hashed = this.bytesToHex(sha256_MAC.doFinal(response.getBytes())).toUpperCase();

System.out.println(hashed.equalsIgnoreCase(
"0978A771F4F3395B00455BFBD7ADF732A09DBCFF65DE773009940DAA113248FC"));
}

public String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}