【Python】实现微信支付API v3请求签名

背景

微信支付API v3的请求签名是比较麻烦的,需要读取pem证书进行签名。

微信支付接口文档

如何生成请求签名:https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-generation.html

实现代码

import datetime
import io
import random
import string
from urllib.parse import urlparse, quote, urlencode, urlunparse

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64

import requests
import json

# 证书路径
pem_path = 'path/apiclient_key.pem'
# 商户号
mchid = '16xxxxd3'
# 证书序列号
serial_no = '3Dxxxxxxxxxxxxxxx83'
# 32位API v3 密钥
api_v3_key = 'z8xxxxxxxxxxxxxxxxxxj6'

# 微信
base_url = "https://api.mch.weixin.qq.com"

def generate_random_string(length):
    """
    随机生成字符串
    :param length:
    :return:
    """
    # 选择字母和数字的组合
    characters = string.ascii_letters + string.digits
    # 随机选择字符并组合成字符串
    random_string = ''.join(random.choice(characters) for _ in range(length))
    return random_string

def get_canonical_url(url):
    """
    获取绝对URL
    :param url:
    :return:
    """
    parsed_url = urlparse(url)

    # 获取路径并进行编码
    canonical_url = quote(parsed_url.path)

    # 如果有查询参数,添加到路径后面
    if parsed_url.query:
        canonical_url += "?" + parsed_url.query

    return canonical_url

def build_message(method, url, timestamp, nonce_str, body):
    """
    生成【请求签名串】
    :param method: 获取HTTP请求的方法(GET,POST,PUT)
    :param url: 获取请求的绝对URL,并去除域名部分得到参与签名的URL。如果请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串。
    :param timestamp: 获取发起请求时的系统当前时间戳(秒)
    :param nonce_str: 随机串
    :param body: 获取请求中的请求报文主体,请求方法为GET时,报文主体为空,当请求方法为POST或PUT时,请使用真实发送的JSON报文。 图片上传API,请使用meta对应的JSON报文
    :return:
    """
    if method == 'GET' or body is None:
        body = ''
    url_encode = get_canonical_url(url)
    return f'{method}\n{url_encode}\n{timestamp}\n{nonce_str}\n{body}\n'


def sign(message):
    """
    生成签名
    :param message:
    :return:
    """
    # 从 PEM 文件加载私钥
    with open(pem_path, "rb") as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None,  # 如果私钥有密码,提供相应的密码
        )

    # 使用 SHA256 和 RSA 对消息进行签名
    signature = private_key.sign(
        message.encode('utf-8'),
        padding.PKCS1v15(),
        hashes.SHA256()
    )

    # 使用 Base64 编码签名
    signature_base64 = base64.b64encode(signature).decode('utf-8')
    return signature_base64

def get_token(method, url, body):
    """
    生成token
    :param method: HTTP请求的方法(GET,POST,PUT)
    :param url: 获取请求的绝对URL,并去除域名部分得到参与签名的URL。如果请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串
    :param body: 获取请求中的请求报文主体,
    :return:
    """
    # 签名算法
    schema = "WECHATPAY2-SHA256-RSA2048"
    nonce_str = generate_random_string(10)
    timestamp = int(datetime.datetime.now().timestamp())

    # 签名
    signature = sign(build_message(method, url, timestamp, nonce_str, body))
    return f'{schema} mchid="{mchid}",nonce_str="{nonce_str}",timestamp="{timestamp}",serial_no="{serial_no}",signature="{signature}"'


def decrypt(key, nonce, ciphertext, associated_data):
    """
    解密证书
    :param key:
    :param nonce:
    :param ciphertext:
    :param associated_data:
    :return:
    """

    # 32位 ApiV3Key
    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = base64.b64decode(ciphertext)
    aesgcm = AESGCM(key_bytes)
    return aesgcm.decrypt(nonce_bytes, data, ad_bytes)


def test_token():
    # 证书接口
    url = f'{base_url}/v3/certificates'

    # 获取token
    access_token = get_token('GET', url, '')
    print(access_token)

    # 设置header
    header = {
        'Authorization': access_token
    }

    response = requests.get(url, headers=header)
    json_data = response.json()
    print(json_data)

    #  解析证书
    encrypt_certificate = json_data['data'][0]['encrypt_certificate']
    print(decrypt(api_v3_key, encrypt_certificate['nonce'], encrypt_certificate['ciphertext'], encrypt_certificate['associated_data']))



# 验证随机字符串
print(generate_random_string(10))

# 测试获取证书,请求证书
test_token()

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄旺鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值