Files
a_long/utils/wechat_v3.py
2026-06-14 02:38:05 +08:00

207 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# utils/wechat_v3.py
'''import time
import hashlib
import base64
import json
from urllib.parse import urlparse
import rsa
from Crypto.Cipher import AES
from django.conf import settings
def load_private_key():
"""加载商户私钥PEM格式"""
with open(settings.WECHAT_PAY_V3_CONFIG['PRIVATE_KEY_PATH'], 'rb') as f:
key_data = f.read()
return rsa.PrivateKey.load_pkcs1(key_data)
def build_authorization(method, url, body):
"""
生成V3接口的Authorization头
:param method: GET/POST
:param url: 完整URL含域名和路径
:param body: 请求体JSON字符串
:return: Authorization字符串
"""
private_key = load_private_key()
mchid = settings.WECHAT_PAY_V3_CONFIG['MCHID']
serial_no = settings.WECHAT_PAY_V3_CONFIG['CERT_SERIAL_NO']
# 提取URL路径不含query
parsed = urlparse(url)
path = parsed.path
if parsed.query:
path += '?' + parsed.query
timestamp = str(int(time.time()))
nonce = hashlib.md5((timestamp + 'wechat').encode()).hexdigest()[:32]
# 构造签名串
message = '\n'.join([method, path, timestamp, nonce, body]) + '\n'
# 使用私钥签名SHA256
signature = rsa.sign(message.encode('utf-8'), private_key, 'SHA-256')
signature_b64 = base64.b64encode(signature).decode('utf-8')
# 组装Authorization
auth = f'WECHATPAY2-SHA256-RSA2048 mchid="{mchid}",nonce_str="{nonce}",timestamp="{timestamp}",serial_no="{serial_no}",signature="{signature_b64}"'
return auth
def verify_wechat_sign(headers, body):
"""
验证微信回调的签名
需要提前从微信平台下载证书公钥,存放在 PLATFORM_CERT_DIR 下,文件名 = 序列号.pem
:param headers: 请求头
:param body: 原始请求体字符串
:return: bool
"""
serial = headers.get('Wechatpay-Serial')
signature = headers.get('Wechatpay-Signature')
timestamp = headers.get('Wechatpay-Timestamp')
nonce = headers.get('Wechatpay-Nonce')
if not all([serial, signature, timestamp, nonce]):
return False
# 构造验签名串
message = '\n'.join([timestamp, nonce, body]) + '\n'
# 读取对应序列号的平台证书公钥
cert_path = f"{settings.WECHAT_PAY_V3_CONFIG['PLATFORM_CERT_DIR']}/{serial}.pem"
try:
with open(cert_path, 'rb') as f:
pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(f.read())
except FileNotFoundError:
# 如果证书不存在,可尝试下载(生产环境建议定时下载)
return False
signature_bytes = base64.b64decode(signature)
try:
rsa.verify(message.encode('utf-8'), signature_bytes, pub_key)
return True
except rsa.VerificationError:
return False
def decrypt_callback_ciphertext(associated_data, nonce, ciphertext):
"""
使用APIv3密钥解密回调中的加密数据
:param associated_data: 附加数据
:param nonce: 随机串
:param ciphertext: 密文Base64
:return: 解密后的JSON字符串
"""
api_v3_key = settings.WECHAT_PAY_V3_CONFIG['API_V3_KEY'].encode('utf-8')
nonce_bytes = nonce.encode('utf-8')
ciphertext_bytes = base64.b64decode(ciphertext)
# AES-GCM解密
cipher = AES.new(api_v3_key, AES.MODE_GCM, nonce=nonce_bytes)
if associated_data:
cipher.update(associated_data.encode('utf-8'))
plaintext = cipher.decrypt_and_verify(ciphertext_bytes[:-16], ciphertext_bytes[-16:])
return plaintext.decode('utf-8')'''
# utils/wechat_v3.py
import time
import hashlib
import base64
import json
from urllib.parse import urlparse
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from Crypto.Cipher import AES
from django.conf import settings
import os
def load_private_key():
"""加载私钥(支持 PKCS#1 和 PKCS#8"""
key_path = settings.WECHAT_PAY_V3_CONFIG['PRIVATE_KEY_PATH']
with open(key_path, 'rb') as f:
key_data = f.read()
# cryptography 自动识别格式
private_key = serialization.load_pem_private_key(
key_data,
password=None,
backend=default_backend()
)
return private_key
def build_authorization(method, url, body):
"""生成V3接口的Authorization头"""
private_key = load_private_key()
mchid = settings.WECHAT_PAY_V3_CONFIG['MCHID']
serial_no = settings.WECHAT_PAY_V3_CONFIG['CERT_SERIAL_NO']
parsed = urlparse(url)
path = parsed.path
if parsed.query:
path += '?' + parsed.query
timestamp = str(int(time.time()))
nonce = hashlib.md5((timestamp + 'wechat').encode()).hexdigest()[:32]
# 构造签名串
message = '\n'.join([method, path, timestamp, nonce, body]) + '\n'
# 使用 cryptography 签名
signature = private_key.sign(
message.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
signature_b64 = base64.b64encode(signature).decode('utf-8')
auth = f'WECHATPAY2-SHA256-RSA2048 mchid="{mchid}",nonce_str="{nonce}",timestamp="{timestamp}",serial_no="{serial_no}",signature="{signature_b64}"'
return auth
def verify_wechat_sign(headers, body):
"""验证微信回调签名(需提前下载平台证书)"""
# 这部分使用 rsa 库验签,也可以改用 cryptography但 rsa 已足够
import rsa
serial = headers.get('Wechatpay-Serial')
signature = headers.get('Wechatpay-Signature')
timestamp = headers.get('Wechatpay-Timestamp')
nonce = headers.get('Wechatpay-Nonce')
if not all([serial, signature, timestamp, nonce]):
return False
message = '\n'.join([timestamp, nonce, body]) + '\n'
cert_path = f"{settings.WECHAT_PAY_V3_CONFIG['PLATFORM_CERT_DIR']}/{serial}.pem"
if not os.path.exists(cert_path):
return False
with open(cert_path, 'rb') as f:
pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(f.read())
signature_bytes = base64.b64decode(signature)
try:
rsa.verify(message.encode('utf-8'), signature_bytes, pub_key)
return True
except rsa.VerificationError:
return False
def decrypt_callback_ciphertext(associated_data, nonce, ciphertext):
"""解密回调数据(使用 AES-GCM"""
api_v3_key = settings.WECHAT_PAY_V3_CONFIG['API_V3_KEY'].encode('utf-8')
nonce_bytes = nonce.encode('utf-8')
ciphertext_bytes = base64.b64decode(ciphertext)
cipher = AES.new(api_v3_key, AES.MODE_GCM, nonce=nonce_bytes)
if associated_data:
cipher.update(associated_data.encode('utf-8'))
plaintext = cipher.decrypt_and_verify(ciphertext_bytes[:-16], ciphertext_bytes[-16:])
return plaintext.decode('utf-8')