Day04 - [丰收款] 金流API的起手式,每次沟通都机密

昨天用了Postman先小试身手,从测试环境取得了必要的发语词Nonce的值(还记得这个60秒就会过期了吧),现在要来了解一下正式使用API沟通的时候会需要的一些流程。

接下来其实不会以永丰开发规格书的顺序来实作,会以我认为较符合情境顺序的方式来说明。首先我们在呼叫API前,要先拆解一下所需要的必要项目。

在规格书中会看到这张图:

https://ithelp.ithome.com.tw/upload/images/20210918/20130354B5NxWkdKmH.png

主要说明的是每一次的API呼叫(不管你是要用哪一种服务,举凡是要建立订单用信用卡支付、还是要查询付款状态等),都会需要的6个栏位以及其来源提供方和组成的方式。

所以先搞定这个麻烦的起手式组成,接下来才能好好的来看应用面。

除了开发规格书有整理的表面,我也针对这6个必要栏位作说明:(我认为要先解释Message後,才解释Sign比较清楚)

参数 说明
Version API版本,目前能传的为固定值:"1.0.0"
ShopNo 我们从永丰取得的商店代号固定值:"NA0249_001"
APIService 要呼叫的API服务名称,目前有四支可呼叫:"OrderCreate""OrderQuery""OrderPayQuery"
Nonce 从Nonce API服务中取得,每次呼叫都要记得拿新的放入。
Message 经AES加密演算法後的交易讯息内容密文注:运算细节文章中再来说明
Sign 安全签章,主要用途是API接收端可用来验证上面的Message在传递过程中没有被非法篡改过。注:运算细节文章中再来说明

很显然的,除了那些固定或选择性范围的值之外,我们必须花一点篇幅来解释一下的是MessageSign这两个栏位,这两个看似复杂的栏位也是银行依金管会所颁布的「电子银行业务安全控管作业基准」的安全设计原则中有关「讯息隐密性」以及「讯息完整性」、「无法否认传送讯息」等而对应的设计。

Message栏位的组成

Message栏位的组成,依开发规格书说明如下:

可还原的原始交易讯息内文 +HashID (32 位元值 )+IV (16 位元值) 三要素进行 AES CBC 加密产生 。

因此从这句话来拆解,又可分为其中三个要素:

  • 讯息内文 (JSON格式,规格需依所需叫用的服务而定)
  • Hash ID (从我们一开始Email中取得的4个Hash神秘编码运算而来)
  • IV值 (Initial Vector,初始向量值)

先准备好上面三个要素後,再把它们用AES加密演算法(CBC)掺在一起做成撒尿牛丸(?),上面所需要的Message栏位就是这样来的。

不过在看了开发规格书中的加/解密范例网址,「Hash ID」这个名称似乎有些前後命名不一致,在该网址中是称作「AES Key」,我比较偏好的是这个命名。

总之,在AES-CBC加密过程会需要加密金钥(AES Key)以及初始向量(IV),将原本明文的内文加密成密文。
接下来就是一一把这过程所需的要素准备好。

讯息内文

依据要呼叫的不同API服务,会有定义所需要传递给永丰API银行端的一套讯息内文栏位。

我们就先以建立订单交易 (信用卡订单)作为范例,目前我们的重点是在於理解Message与Sign的产生过程,因此讯息内文可先以开发规格书中的范例作内容即可(先不用理解每个JSON栏位的意义),後续我们套用到自己的情境时再来讲究内容的设计与选用。

{
    "ShopNo": "NA0249_001",
    "OrderNo": "C201804300001",
    "Amount": 50000,
    "CurrencyID": "TWD",
    "PayType": "C",
    "CardParam": { "AutoBilling": "Y" },
    "PrdtName": "信用卡订单",
    "ReturnURL": "http://10.11.22.113:8803/QPay.ApiClient/Store/Return",
    "BackendURL": "http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"
}

以上就是我们所需要的讯息内文的JSON内容,接着往下看。

Hash ID

也就是上面谈到的「AES Key」(加密金钥),这个Hash ID栏位的组成,依开发规格书说明如下:

Hash ID 是透过位元运算(XOR)将四组 Hash 计算产出的, 将 A1/A2 以 XOR 运 算所得的字串, 再 与 B1/B2 以 XOR 运算 出来的字串,二个相加後将英文转换为大写, 为长度为 32 的字串 (例 : 17D8E6558DC60E702A6B57E1B9B7060D)

https://ithelp.ithome.com.tw/upload/images/20210918/201303541HsP5BbZO7.png

快把Email中收到的四个Hash码拿出来,前两个一组做XOR运算,後两个一组XOR运算,再把两组运算後的字串拼接在一起後,转成全大写,这就是我们需要的Hash ID。

我们直接使用Python来跑一次:

A1, A2, B1, B2 = "86D50DEF3EB7400E", "01FD27C09E5549E5", "9E004965F4244953", "7FB3385F414E4F91"

def bytes_xor_to_hexstring(ba1, ba2):
    return bytes([a ^ b for a, b in zip(ba1, ba2)]).hex()
    
ba_xor_A = bytes_xor_to_hexstring(bytes.fromhex(A1), bytes.fromhex(A2))
ba_xor_B = bytes_xor_to_hexstring(bytes.fromhex(B1), bytes.fromhex(B2))

hash_id = "{}{}".format(ba_xor_A, ba_xor_B).upper()
print(hash_id)  
#output: 87282A2FA0E209EBE1B3713AB56A06C2
程序说明

在上面的bytes_xor_to_hexstring() function中,主要是可接受两个一组的Hash参数值(bytes型别),并将这两组bytes使用zip()将每个同index的元素组成两两一组,再进行XOR的运算,最终将这样的运算结果以Python独特的List comprehension产生新的XOR结果的List。

由於XOR後会是一般的List,因此需要使用bytes()转型後,再使用hex()方法将其结果转换为16进位(HEX)字串。

在呼叫上述function之前,需要使用bytes.fromhex()将16进位样式的字串转换成bytes,才可传入bytes_xor_to_hexstring()中。

最後将A1/A2产生的字串结果和B1/B2产生的字串结果作字串拼接起来再一起使用upper()转成大写,这就是我们的目标Hash ID的值。

Production版本的扩充建议:

若是真的要运用在Production环境,可以考虑到以下几点:

  • 进行四个Hash值的特定长度以及16进位字多的检查
  • 两两进行比对时确认长度相同 (第一点若有实作,其实已可满足)
  • 这四个值即使看起来不会有所变动,但仍然应从外部属性档中读入,而非写死在程序码中

另外补充说明一下,既然是AES的对称加密,因此我们辛苦产生的这把金钥:Hash ID,既然原先的4组Hash代码是由永丰提供给我们,就表示对於我们这个商家而言,他们也必然会拥有和我们一样的Hash ID。以便我们在未来作加密後的API沟通的内容,永丰API在收到密文的内容是有办法使用解密法解开,还原回原文。

好罗,把AES-CBC加密需要的金钥(Hash ID)也准备好了,接下来就是IV值的准备!明日再写。


<<:  Day-7 Pipeline

>>:  D3JsDay04一同来见识 D3起手式—用D3写Helloworld

Day27条件选择器switch(JavaScript)

switch 主要功能是依据不同的条件去执行其动作 他基本型态会长这样 switch (expres...

Day19 Redis架构实战-持久化AOF

Redis持久化 Redis持久化模式->AOF AOF (Append Only File)...

全端入门Day24_後端程序撰写之多一点点的Node.js

昨天介绍了一些名词,今天继续提Node.js Node.js一点入门 今天直接贴上程序码,再去做解释...

【左京淳的Spring学习笔记】基础案例

使用首页、输入画面、输出画面等三个基础画面,来熟悉画面之间跳转及资料移动的原理。 本练习不涉及业务...

【Day 24】Google Apps Script - API Blueprint 篇 - Google Docs 转换 API Blueprint 格式(2)

继续介绍昨天主流程里的副程序吧。 今日要点: 》Google Docs 转换 API Bluepr...