加密前的资料在前几天我们都有拿到了!接着就是实作 AES-CBC 罗~
流程如下图
关於 AES-CBC,可以参考 Wiki
不过我还是稍微讲一下,从图片可以看出要进行 AES-CBC,需要有几样东西
Plaintext 我们已经有了,就是昨天的 JSON 讯息内文
IV 我们也有,就是将 Nonce 经过 SHA256 杂凑後取右边十六个字元
Key 我们也有,就是前几天计算出来的 Hash ID
但是需要知道的不只是这样。
在 AES-CBC 需要将讯息填充 (padding) 到 block size (16 Bytes) 的整数倍
然而填充方式有许多种,需要知道哪一种才能正确将讯息加密~
PHP Sample Code 的 function EncryptAesCBC
的这两行程序码就是在提示永丰他们使用哪一种 padding
// 看还差多少 bytes 才到 16 的整数倍
$padding = 16 - (strlen($data) % 16);
// 使用 chr($padding) 当作 padding 重复 $padding 次
$data .= str_repeat(chr($padding), $padding);
这方式就是 PKCS7
!
如果以 block size 为 8 Bytes 的情况下做举例
# DD 为我们要加密的明文,可是不足 8 的整数倍,缺少 4 bytes,於是填充 4 个 04
... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
# DD 为我们要加密的明文,可是不足 8 的整数倍,缺少 5 bytes,於是填充 5 个 05
... | DD DD DD DD DD DD DD DD | DD DD DD 05 05 05 05 05 |
了解上面的东西後,我们就可以按照图片的流程,生产出 Ciphertext 了!
程序码部分也不难,当然要先装一下必须的 module PyCryptodome
pip install pycryptodome
如何使用可以参考 AES CBC 的说明
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
def CBCEncrypt(key, iv, data):
## new 一个 AES CBC cipher
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
## 将要加密的 data encode 成 utf-8
## 然後使用 pad function 将明文 padding 到 block size
## https://github.com/Legrandin/pycryptodome/blob/master/lib/Crypto/Util/Padding.py#L39
## 从上面网址可以知道他预设使用 pkcs7 这种 padding 方式,我们不需要做任何事情
return (cipher.encrypt(pad(data.encode('utf-8'), AES.block_size)))
enc_msg = CBCEncrypt(hash_id, iv, msg)
然後永丰也很贴心有提供网址让使用者去测试自己是否有成功加密!
加密成功後,当然就是要解的回来呀!
解密流程跟加密没有差很多就不多做解释了
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import codecs
def CBCDecrypt(key, iv, data):
data = codecs.decode(data, "hex") ## 或是要用 bytes.fromhex(data) 也行
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
return unpad(cipher.decrypt(data), AES.block_size)
dec_msg = CBCDecrypt(hash_id, iv, enc_message)
永丰也有提供解密网页让使用者尝试
没错,就这样大功告成了!!!
一样在最後附上到目前为止的 code
from __future__ import unicode_literals
import requests
import hashlib
import codecs
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
shop_no = '<shop_no>'
sinopac_hash = {
'a1': '',
'a2': '',
'b1': '',
'b2': ''
}
nonce_url = 'https://apisbx.sinopac.com/funBIZ/QPay.WebAPI/api/Nonce'
order_url = 'https://apisbx.sinopac.com/funBIZ/QPay.WebAPI/api/Order'
def getNonce():
nonce_data = {
'ShopNo': shop_no
}
r = requests.post(nonce_url, json=nonce_data)
return json.loads(r.content)['Nonce']
def calcHashID():
a1 = sinopac_hash['a1']
a2 = sinopac_hash['a2']
b1 = sinopac_hash['b1']
b2 = sinopac_hash['b2']
xor1 = hex(int(a1, base=16)^int(a2, base=16))
xor2 = hex(int(b1, base=16)^int(b2, base=16))
hash_id = xor1[2:]+xor2[2:]
return hash_id.upper()
def calcIV(nonce):
s = hashlib.sha256()
s.update(nonce.encode('utf-8'))
h = s.hexdigest()
return h[-16:].upper()
def calcSign(msg_content, nonce, hash_id):
sign_msg = msg_content+nonce+hash_id
s = hashlib.sha256()
s.update(sign_msg.encode('utf-8'))
h = s.hexdigest()
return h.upper()
def parseQueryData(msg_param):
if type(msg_param) != dict:
return
order_message = dict(sorted(msg_param.items(), key = lambda x: x[0]))
message = ''
for k, v in order_message.items():
if type(v) == dict or v == '':
continue
message += f"{k}={v}&"
return message[:-1]
def CBCEncrypt(key, iv, data):
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
return (cipher.encrypt(pad(data.encode('utf-8'), AES.block_size)))
def CBCDecrypt(key, iv, data):
data = codecs.decode(data, "hex")
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
return unpad(cipher.decrypt(data), AES.block_size)
order_create = {
"ShopNo": shop_no,
"OrderNo": "A202109120010",
"Amount": 50000,
"CurrencyID": "TWD",
"PayType": "C",
"CardParam": {
"AutoBilling": "Y"
},
"ConvStoreParam": { },
"PrdtName": "信用卡订单",
"ReturnURL": "http://10.11.22.113:8803/QPay.ApiClient/Store/Return",
"BackendURL": "http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"
}
nonce = getNonce()
hash_id = calcHashID()
iv = calcIV(nonce)
content = parseQueryData(order_create)
signature = calcSign(content, nonce, hash_id)
msg = json.dumps(order_create, ensure_ascii=False).replace(' ','')
print(msg)
enc_msg = CBCEncrypt(hash_id, iv, msg).hex().upper()
dec_msg = CBCDecrypt(hash_id, iv, enc_msg)
print(dec_msg)
"""
msg:
{"ShopNo":"<shop_no>","OrderNo":"A202109120010","Amount":50000,"CurrencyID":"TWD","PayType":"C","CardParam":{"AutoBilling":"Y"},"ConvStoreParam":{},"PrdtName":"信用卡订单","ReturnURL":"http://10.11.22.113:8803/QPay.ApiClient/Store/Return","BackendURL":"http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"}
dec_msg:
{"ShopNo":"<shop_no>","OrderNo":"A202109120010","Amount":50000,"CurrencyID":"TWD","PayType":"C","CardParam":{"AutoBilling":"Y"},"ConvStoreParam":{},"PrdtName":"\xe4\xbf\xa1\xe7\x94\xa8\xe5\x8d\xa1\xe8\xa8\x82\xe5\x96\xae","ReturnURL":"http://10.11.22.113:8803/QPay.ApiClient/Store/Return","BackendURL":"http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"}
"""
### "PrdtName":"\xe4\xbf\xa1\xe7\x94\xa8\xe5\x8d\xa1\xe8\xa8\x82\xe5\x96\xae" 是信用卡订单
所以到目前为止 API 所需要内容我们都有了!
明天我们就正式开始发 API Request 了~
<<: Material UI in React [ Day13 ] Inputs (slider) 调整滑块
视讯会议时,你都偷偷在做什麽? 蓬勃发展的视讯会议软件 拜 covid-19 所赐,因爲许多人的工作...
今天要利用之前学到的东西写一个猜数字游戏,此猜数字游戏要符合以下条件: 1 猜数字范围介於0-99间...
综合本系列此前的汇整,构成JavaScript资料的基本型别(primitives)是指字串、数字、...
#以下内容皆由初学者撰写,有错误可能,不建议尽信 30天Python自学: Day01 Python...
在前两篇简单操作了阿里上面的ACK服务,今天要分享的是使用ingress,怎麽用一个IP去分享多个服...