shop_platform - 串接藍新金流:TradeInfo

(本文使用的語言為 python 3)

文件:藍新金流 > API 文件下載 > 多功能收款 MPG。我下載時,版本是 1.6

TradeInfo 是什麼

根據文件第 33 頁的第一個表格,需要傳送給藍新金流的參數共有四個,其中一個就是 TradeInfo:

需要放進 TradeInfo 裡的參數欄位,如文件第 33~37 頁所列。準備好這些參數欄位後,要幫這些參數欄位加密,加密後的結果,才是要傳送給藍新金流的 TradeInfo。

加密

根據文件第 65~66 頁,可以看到,要先把參數欄位整理成 query string 的格式,才進行 AES 加密。AES 加密採用 CBC 模式,padding 的 blocksize 是 32。然後把加密後的結果轉成 16 進位。test case 為:

data = {
    'MerchantID': 3430112,
    'RespondType': 'JSON',
    'TimeStamp': 1485232229,
    'Version': 1.4,
    'MerchantOrderNo': 'S_1485232229',
    'Amt': 40,
    'ItemDesc': 'UnitTest'
}
Key = '12345678901234567890123456789012'
IV = '1234567890123456'
expect_output = 'ff91c8aa01379e4de621a44e5f11f72e4d25bdb1a18242db6cef9ef07d80b0165e476fd1d9acaa53170272c82d122961e1a0700a7427cfa1cf90db7f6d6593bbc93102a4d4b9b66d9974c13c31a7ab4bba1d4e0790f0cbbbd7ad64c6d3c8012a601ceaa808bff70f94a8efa5a4f984b9d41304ffd879612177c622f75f4214fa'

query string 格式

將不同的 key-value pair 用 & 隔開,key 和 value 之間用 = 隔開。像這樣:key1=value1&key2=value2

也就是說,test case 中的 data 轉換成 query string 格式後會長這樣:

MerchantID=3430112&RespondType=JSON&TimeStamp=1485232229&Version=1.4&MerchantOrderNo=S_1485232229&Amt=40&ItemDesc=UnitTest

data 轉 query string 實作:

from typing import Dict

def query_string_encode(data: Dict) -> str:
    key_value_pairs = []
    for key, value in data.items():
        key_value_pairs.append(f'{key}={value}')

    return '&'.join(key_value_pairs)

AES 加密

密鑰(key)和初始向量(iv),會在註冊藍新金流帳號時拿到。密鑰是英文大小寫和數字組成的,共有 32 個字。我目前的猜測是,這組密鑰轉成 ASCII 編碼的話,ASCII 編碼中的每個字元大小是 8 bits (= 1 byte),那麼 32 個字的密鑰,就是 32 * 8 bits = 256 bits,所以會被歸類在 AES 256 加密。

搜尋怎麼用 python 完成 AES 加密,查詢到的套件有 PyCrypto(已經停止維護,網友不推薦使用)、Pycryptodome(或者又叫 Pycryptodomex,結尾多一個 x)。似乎比較多人在用 Pycryptodome,最後使用 Pycryptodome。

AES 加密實作:

from typing import Dict, Union

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def aes_encrypt(data: str,
                key: bytes = HashKey.encode('ascii'),
                iv: bytes = HashIV.encode('ascii')) -> str:
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    ciphered_data = cipher.encrypt(pad(data.encode('utf8'), block_size=32))
    return ciphered_data.hex()

有些參數只接受二進制或 bytearray 類型的資料,所以我用 string.encode() 函式將字串轉成二進制。因為我使用的 data 中可能有中文,所以是用 utf-8 的編碼方式轉成二進制資料。

PyCharm 一直提醒我:Package containing module 'Crypto' is not listed in the project requirements。看了幾篇關於 crypto、pycrypto、pycryptodome 、pycryptodomex 這幾個套件之間到底是什麼關係的文章後,我目前的理解是:

  • crypto 是 cli 用的。

  • pycrypto 多年前已停止維護,繼任者是 pycryptodome。

  • pycryptodome 和 pycryptodomex 功能是一樣的,他們之間的差別在於安裝的位置不同。前者會把套件安裝在 Crypto package 裡面,和 pycrypto 的安裝位置相同,所以 pycrypto 和 pycryptodome 不能並存;而後者則是安裝在 Cryptodome package 裡面,所以能夠和 pycrypto 並存。

我想,既然我的 Pipfile 裡面已經有寫「pycryptodome = "*"」,我的程式碼應該不會壞掉,所以就不管這個警告了。如果有人真的不喜歡這個警告,那麼我推測,不要安裝 pycryptodome,改成安裝 pycryptodomex 應該就可以解決了。

commit

參考資料

Comments

Popular posts from this blog

Alpha Camp 全端開發課程學習心得

在 javascript 用 regular expression 為金額加上千位數分隔符號

shop_platform - sqlalchemy.exc.TimeoutError