Skip to content

Commit bd9c1fa

Browse files
committed
验证2xx应答签名
1 parent 7edcab1 commit bd9c1fa

File tree

6 files changed

+78
-28
lines changed

6 files changed

+78
-28
lines changed

examples.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# -*- coding: utf-8 -*-
2-
from config import MCH_KEY_SERIAL_NO, MCHID, WECHAT_PUBLIC_KEY, MCH_PRIVATE_KEY, APPID, NOTIFY_URL
2+
from config import MCH_KEY_SERIAL_NO, MCHID, WECHAT_CERTIFICATE, MCH_PRIVATE_KEY, APPID, NOTIFY_URL
33

44
from wechatpayv3 import WeChatPay, WeChatPayType
55

66
wxpay = WeChatPay(wechatpay_type=WeChatPayType.MINIPROG,
77
mchid=MCHID,
88
mch_parivate_key=MCH_PRIVATE_KEY,
99
mch_key_serial_no=MCH_KEY_SERIAL_NO,
10-
wechat_public_key=WECHAT_PUBLIC_KEY,
10+
wechat_certificate=WECHAT_CERTIFICATE,
1111
appid=APPID,
1212
notify_url=NOTIFY_URL)
1313

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
setup(
88
name="wechatpayv3",
9-
version="0.45",
9+
version="0.46",
1010
author="minibear",
1111
description="微信支付接口V3版python库",
1212
long_description=long_description,

wechatpayv3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# -*- coding: utf-8 -*-
2-
from .api import WeChatPay, WeChatPayException, WeChatPayType
2+
from .api import WeChatPay, WeChatPayType

wechatpayv3/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ def __init__(self,
1313
mch_key_serial_no,
1414
appid,
1515
notify_url=None,
16-
wechat_public_key=None):
16+
wechat_certificate=None):
1717
"""
1818
:param wechatpay_type: 微信支付类型,示例值:WeChatPayType.MINIPROG
1919
:param mchid: 直连商户号,示例值:'1230000109'
2020
:param mch_private_key: 商户证书私钥,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...'
2121
:param mch_key_serial_no: 商户证书序列号,示例值:'444F4864EA9B34415...'
2222
:param appid: 应用ID,示例值:'wxd678efh567hg6787'
2323
:param notify_url: 通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php'
24-
:param wechat_public_key:微信支付平台证书公钥,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...'
24+
:param wechat_certificate:微信支付平台证书,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...'
2525
"""
2626
self._type = wechatpay_type
2727
self._mchid = mchid
@@ -30,7 +30,7 @@ def __init__(self,
3030
self._core = Core(mchid=self._mchid,
3131
mch_key_serial_no=mch_key_serial_no,
3232
mch_private_key=mch_parivate_key,
33-
wechat_public_key=wechat_public_key)
33+
wechat_certificate=wechat_certificate)
3434

3535
def pay(self,
3636
description,

wechatpayv3/core.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,59 @@
11
# -*- coding: utf-8 -*-
2-
from .utils import build_authorization
2+
from .utils import build_authorization, verify_response, certificate_serial_number
33
import requests
44
import json
5+
6+
57
class Core():
6-
def __init__(self, mchid, mch_key_serial_no, mch_private_key, wechat_public_key):
8+
def __init__(self, mchid, mch_key_serial_no, mch_private_key, wechat_certificate):
79
self._mchid = mchid
810
self._mch_key_serial_no = mch_key_serial_no
911
self._mch_private_key = mch_private_key
10-
self._wechat_public_key = wechat_public_key
12+
self._wechat_certificate = wechat_certificate
1113
self._gate_way = 'https://api.mch.weixin.qq.com'
1214

1315
def get(self, path):
1416
headers = {}
1517
headers.update({'Content-Type': 'application/json'})
1618
headers.update({'Accept': 'application/json'})
17-
headers.update({'User-Agent': 'wechatpay v3 python sdk'})
18-
headers.update({'Authorization': build_authorization(
19-
path, 'GET', self._mchid, self._mch_key_serial_no, self._mch_private_key)})
19+
headers.update(
20+
{'User-Agent': 'wechatpay v3 python sdk(https://github.com/minibear2021/wechatpayv3)'})
21+
authorization = build_authorization(
22+
path, 'GET', self._mchid, self._mch_key_serial_no, self._mch_private_key)
23+
headers.update({'Authorization': authorization})
2024
response = requests.get(url=self._gate_way + path, headers=headers)
21-
return response.status_code, response.text
25+
if response.status_code in [200, 202, 204]:
26+
timestamp = response.headers.get('Wechatpay-Timestamp')
27+
nonce = response.headers.get('Wechatpay-Nonce')
28+
signature = response.headers.get('Wechatpay-Signature')
29+
body = response.content
30+
serial_no = response.headers.get('Wechatpay-Serial')
31+
if serial_no != certificate_serial_number(self._wechat_certificate):
32+
return -1, '{"message": "微信支付平台证书序号不一致"}'
33+
if not verify_response(timestamp, nonce, body, signature, self._wechat_certificate):
34+
return -1, '{"message": "应答签名验证失败"}'
35+
return response.status_code, response.content
2236

2337
def post(self, path, data=None):
2438
headers = {}
2539
headers.update({'Content-Type': 'application/json'})
2640
headers.update({'Accept': 'application/json'})
27-
headers.update({'User-Agent': 'wechatpay v3 python sdk'})
28-
authorization = build_authorization(path, 'POST', self._mchid, self._mch_key_serial_no, self._mch_private_key, data=json.dumps(data))
41+
headers.update(
42+
{'User-Agent': 'wechatpay v3 python sdk(https://github.com/minibear2021/wechatpayv3)'})
43+
authorization = build_authorization(
44+
path, 'POST', self._mchid, self._mch_key_serial_no, self._mch_private_key, data=json.dumps(data))
2945
headers.update({'Authorization': authorization})
3046
response = requests.post(self._gate_way + path,
3147
json=data,
3248
headers=headers)
33-
return response.status_code, response.text
49+
if response.status_code in [200, 202, 204]:
50+
timestamp = response.headers.get('Wechatpay-Timestamp')
51+
nonce = response.headers.get('Wechatpay-Nonce')
52+
signature = response.headers.get('Wechatpay-Signature')
53+
body = response.content
54+
serial_no = response.headers.get('Wechatpay-Serial')
55+
if serial_no != certificate_serial_number(self._wechat_certificate):
56+
return -1, '{"message": "微信支付平台证书序号不一致"}'
57+
if not verify_response(timestamp, nonce, body, signature, self._wechat_certificate):
58+
return -1, '{"message": "应答签名验证失败"}'
59+
return response.status_code, response.content

wechatpayv3/utils.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# -*- coding: utf-8 -*-
22

3-
import base64
43
import time
54
import uuid
5+
from base64 import b64decode, b64encode
66

77
from cryptography.hazmat.backends import default_backend
88
from cryptography.hazmat.primitives import serialization
99
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
1010
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
1111
from cryptography.hazmat.primitives.hashes import SHA256
12+
from OpenSSL import crypto
1213

1314

1415
def build_authorization(path,
@@ -23,26 +24,26 @@ def build_authorization(path,
2324
body = data if data else ''
2425
sign_str = method + '\n' + path + '\n' + \
2526
timeStamp + '\n' + nonce_str + '\n' + body + '\n'
26-
signature = sign(mch_private_key=mch_private_key, sign_str=sign_str)
27+
signature = sign(private_key=mch_private_key, sign_str=sign_str)
2728
authorization = 'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"' % (
2829
mchid, nonce_str, signature, timeStamp, serial_no)
2930
return authorization
3031

3132

32-
def sign(mch_private_key, sign_str):
33+
def sign(private_key, sign_str):
3334
private_key = serialization.load_pem_private_key(data=format_private_key(
34-
mch_private_key).encode('UTF-8'), password=None, backend=default_backend())
35+
private_key).encode('UTF-8'), password=None, backend=default_backend())
3536
message = sign_str.encode('UTF-8')
3637
signature = private_key.sign(message, PKCS1v15(), SHA256())
37-
sign = base64.b64encode(signature).decode('UTF-8').replace('\n', '')
38+
sign = b64encode(signature).decode('UTF-8').replace('\n', '')
3839
return sign
3940

4041

4142
def decrypt(nonce, ciphertext, associated_data, apiv3_key):
4243
key_bytes = apiv3_key.encode('UTF-8')
4344
nonce_bytes = nonce.encode('UTF-8')
4445
associated_data_bytes = associated_data.encode('UTF-8')
45-
data = base64.b64decode(ciphertext)
46+
data = b64decode(ciphertext)
4647
aesgcm = AESGCM(key_bytes)
4748
return aesgcm.decrypt(nonce_bytes, data, associated_data_bytes).decode('UTF-8')
4849

@@ -57,11 +58,34 @@ def format_private_key(private_key):
5758
return private_key
5859

5960

60-
def format_public_key(public_key):
61+
def format_certificate(certificate):
6162
pem_start = '-----BEGIN CERTIFICATE-----\n'
6263
pem_end = '\n-----END CERTIFICATE-----'
63-
if not public_key.startswith(pem_start):
64-
public_key = pem_start + public_key
65-
if not public_key.endswith(pem_end):
66-
public_key = public_key + pem_end
64+
if not certificate.startswith(pem_start):
65+
certificate = pem_start + certificate
66+
if not certificate.endswith(pem_end):
67+
certificate = certificate + pem_end
68+
return certificate
69+
70+
71+
def verify_response(timestamp, nonce, body, signature, certificate):
72+
sign_str = '%s\n%s\n%s\n' % (timestamp, nonce, body)
73+
public_key = dump_public_key(certificate)
74+
message = sign_str.encode('UTF-8')
75+
return public_key.verify(b64decode(signature), sign_str.encode('UTF-8'), PKCS1v15, SHA256)
76+
77+
78+
def certificate_serial_number(certificate):
79+
cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
80+
try:
81+
res = cert.get_signature_algorithm().decode('UTF-8')
82+
if res != 'sha256WithRSAEncryption':
83+
return None
84+
return hex(cert.get_serial_number()).upper()[2:]
85+
except:
86+
return None
87+
88+
def dump_public_key(certificate):
89+
cert = crypto.load_certificate(crypto.FILETYPE_PEM, format_certificate(certificate))
90+
public_key = crypto.dump_publickey(crypto.FILETYPE_PEM, cert.get_pubkey()).decode("utf-8")
6791
return public_key

0 commit comments

Comments
 (0)