iT邦幫忙

2021 iThome 鐵人賽

DAY 2
1

在串接API時,遇到最大的坎就是Message內文加密了,
就讓我們來試看看囉~

Message內文加密

項目 說明
產出JSON訊息內文 即要送出的訊息內文(JSON)
HashID 由四組 Hash 值透過兩兩 XOR 位元運算再相加的32 位元字串。
IV值 Nonce 值經過 SHA256 運算後取右邊 16 位元字串 。

IV 計算

有上一篇的經驗,IV計算就顯得非常親切,步驟如下:

  1. Nonce 值作 SHA256 加密
  2. 將英文轉換成大寫
  3. 取字串右邊 16 碼長度的部分,即為IV,
    就直接看程式吧!
sha256 = hashlib.sha256()
sha256.update(NonceValue.encode('utf-8'))
SHAValue = sha256.hexdigest().upper()
print(SHAValue)

IVValue=SHAValue[-16:]
print(IVValue)

結果

CB6FA68E42B655AB

內文加密(Message)

重頭戲來囉,規格書顯示:
將訊息內文以 AES CBC 方式加密,加密後的 Byte 以十六進制2位數字串相加。
https://ithelp.ithome.com.tw/upload/images/20210920/201409245vrTTeiG4P.png

因為對於實在是沒有經驗,直接針對關鍵字搜尋,下面是我最後參考的一篇文章,
Python AES/CBC/PKCS5Padding加解密
不過雖然使用以上程式,可以正常的加解密,但出來的加密內容,與範例不一樣,因為不熟悉加解密的流程,看範例程式好像也沒有太大頭緒,大概是我PHP也不太會寫,只好於線上搜尋相關問題了,
最後最後~突發奇想,想說找看看線上加密的網頁,
參考如下
線上加解密網址
與網頁上的程式結果一致,但不是我要的答案,這也是友人問我的問題,無法跟範例一致。
結果我發現線上加解密網站,多了一些選項,Output Text Format的選項,多了HEX選項,如下圖,
絕大多數的線上範例,幾乎都是base64的格式,
登登,果然改用HEX之後柳暗花明,終於找到答案了!!!!
https://ithelp.ithome.com.tw/upload/images/20210920/20140924CrQ1VsDXSA.png
規格書上若能說出是用HEX轉碼就省很多麻煩了~~
首先先安裝以下套件

pip install pycryptodome 

程式如下

from Crypto.Cipher import AES


class AESCrypt:
    """
    AES/CBC/PKCS5Padding 加密
    """
    def __init__(self, key,iv):
        """
        使用金鑰,加密模式進行初始化
        :param key:
        """
        if len(key) != 16 and len(key) !=32:
            raise RuntimeError('金鑰長度非16位 and 32 位!!!')

        self.key = str.encode(key)
        self.iv = str.encode(iv)
        self.MODE = AES.MODE_CBC
        self.block_size = 16

        # 填充函數
        # self.padding = lambda data: data + (self.block_size - len(data) % self.block_size) * chr(self.block_size - len(data) % self.block_size)
        # 此處為一坑,需要現將data轉換為byte再來做填充,否則中文特殊字元等會報錯
        self.padding = lambda data: data + (self.block_size - len(data.encode('utf-8')) % self.block_size) * chr(self.block_size - len(data.encode('utf-8')) % self.block_size)
        # 截斷函數
        self.unpadding = lambda data: data[:-ord(data[-1])]

    def aes_encrypt(self, plaintext):
        """
        加密
        :param plaintext: 明文
        :return:
        """
        try:
            # 填充16位
            padding_text = self.padding(plaintext).encode("utf-8")
            # 初始化加密器
            cryptor = AES.new(self.key, self.MODE, self.iv)
            # 進行AES加密
            encrypt_aes = cryptor.encrypt(padding_text)
            # 進行HEX轉碼
            encrypt_text = encrypt_aes.hex()
            # # 進行BASE64轉碼
            # encrypt_text = (base64.b64encode(encrypt_aes)).decode()
            return encrypt_text
        except Exception as e:
            logging.exception(e)

    def aes_decrypt(self, ciphertext):
        """
        解密
        :param ciphertext: 密文
        :return:
        """
        try:
            # 密文必須是16byte的整數倍
            # if len(ciphertext) % 16 != 0:
            #     raise binascii.Error('密文錯誤!')
            # print(ciphertext)
            cryptor = AES.new(self.key, self.MODE, self.iv)
            # 進行BASE64轉碼
            # plain_decode = base64.b64decode(ciphertext)

            # 進行HEX轉碼
            plain_decode = bytes.fromhex(ciphertext)
            # print(type(plain_decode)) #byte
            # 進行ASE解密
            decrypt_text = cryptor.decrypt(plain_decode)
            # 截取
            plain_text = self.unpadding(decrypt_text.decode("utf-8"))
            return plain_text
        except UnicodeDecodeError as e:
            logging.error('解密失敗,請檢查金鑰是否正確!')
            logging.exception(e)
        except binascii.Error as e:
            logging.exception(e)
        except Exception as e:
            logging.exception(e)

if __name__ == '__main__':
    # 測試
    send_message_ori = {
    "ShopNo": "BA0026_001",
    "OrderNo": "A201804270001",
    "Amount": 50000,
    "CurrencyID": "TWD",
    "PayType": "A",
    "ATMParam": {"ExpireDate": "20180502"},
    "CardParam": {},
    "PrdtName": "虛擬帳號訂單",
    "ReturnURL": "http://10.11.22.113:8803/QPay.ApiClient/Store/Return",
    "BackendURL": "http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess",
}
    cryptor = AESCrypt(hashID,IVValue)
    jsonText=json.dumps(send_message_ori, ensure_ascii=False).replace(' ', "")

    aes_encrypt_str = cryptor.aes_encrypt(jsonText).upper()
    print(f'加密結果為: {aes_encrypt_str}')
    aes_decrypt_str = cryptor.aes_decrypt(aes_encrypt_str)
    print(f'解密結果為: {aes_decrypt_str}')

結果如附

加密結果為: 2C236A4E91DB2F7670E79BBCE3A626EB728916919012681FF92BE0B4BBF57F5519AF1A469A1D8710B202CB2C2F3C12A770788D825AD0F0A22AED518545A0D244AD0F9C37C7C693EFFABE78B606BCDAED6284902F7F522BBA85D9BE7EFEF46C6793FB6A5D6624C2642A74EB312034BEA931EE3A5F3C660F3ABAA9032949AE86DEFEB452545807561D282C7B7C8E9102CED1404B8B542BC09CE12FA38F335BE7F027AE74BDDBADDB1790B172EFBF1FD25524E2BB64A626EA44643D4BD490E348E926BB7A48D5FA939EEC5BE681009E7AC7FED1C8475B715891321406960675B5A216032CF8657A3CB2B2D0C7FF85027D70E1F2B5DD414373912E97FA6FB85E9AB89B118BC545583CC9AC503F8BAD73C185CB97B28313618021F9217A30278043EF728BB5C49D231C4A22279864F68194254BC624789F36CCDEE75861CFC667CD8E9E89F1DB04ABA0D26FEF24BFE0470488
解密結果為: {"ShopNo":"BA0026_001","OrderNo":"A201804270001","Amount":50000,"CurrencyID":"TWD","PayType":"A","ATMParam":{"ExpireDate":"20180502"},"CardParam":{},"PrdtName":"虛擬帳號訂單","ReturnURL":"http://10.11.22.113:8803/QPay.ApiClient/Store/Return","BackendURL":"http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"}

加解密的部分都搞定了,剩下呼叫永豐API的部分了


上一篇
[day5]API串接-安全簽章Sign(二)
下一篇
[day7]呼叫永豐API及流程串接整理
系列文
永豐Vue一下-從生活尋找靈感30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言