本文代码是另一位老哥写的,
我在github上找到并修改部分后成功使用,感谢前者,
记录下来以在用,也分享给大家
代码内的注释是之前老哥的
中文部分由CHATGPT解释
总共三个文件
# -*- coding: utf-8 -*-
# @Time : 2021-01-24 14:08
# @Author : makun 15902051493
# @FileName: RSAUtil.py
# @Describe: 创建RSA签名
# pip install rsa -i https://pypi.douban.com/simple
__pem_begin = '-----BEGIN RSA PRIVATE KEY-----\n'
__pem_end = '\n-----END RSA PRIVATE KEY-----'
def RSASign(content, private_key, sign_type):
if sign_type.upper() == 'RSA':
return rsa_sign(content, private_key, 'SHA-1')
elif sign_type.upper() == 'RSA2':
return rsa_sign(content, private_key, 'SHA-256')
else:
raise Exception('sign_type错误')
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
def load_private_key(pem_private_key):
private_key = serialization.load_pem_private_key(
pem_private_key.encode(),
password=None,
backend=default_backend()
)
return private_key
def rsa_sign(content, pem_private_key, hash_algorithm):
private_key = load_private_key(pem_private_key)
if hash_algorithm == 'SHA-256':
algorithm = hashes.SHA256()
elif hash_algorithm == 'SHA-1':
algorithm = hashes.SHA1()
else:
raise ValueError("Unsupported hash algorithm")
signature = private_key.sign(
content.encode(),
padding.PKCS1v15(),
algorithm
)
return signature
def _format_private_key(private_key):
if not private_key.startswith(__pem_begin):
private_key = __pem_begin + private_key
if not private_key.endswith(__pem_end):
private_key = private_key + __pem_end
return private_key
首先,定义了两个常量 __pem_begin
和 __pem_end
,它们包含了RSA私钥的开始和结束标志。
接下来,有以下几个函数:
RSASign(content, private_key, sign_type)
: 这是一个用于进行RSA签名的主要函数。它接收三个参数:content
是要签名的内容,private_key
是RSA私钥(以PEM格式表示),sign_type
是签名类型,可以是 ‘RSA’ 或 ‘RSA2’。根据 sign_type
的不同,它会调用 rsa_sign
函数来进行具体的签名操作。
load_private_key(pem_private_key)
: 这个函数用于加载PEM格式的RSA私钥,并返回一个私钥对象。它接收一个参数 pem_private_key
,这是PEM格式的私钥字符串。函数内部使用 serialization.load_pem_private_key
方法来加载私钥。
rsa_sign(content, pem_private_key, hash_algorithm)
: 这个函数用于对内容进行RSA数字签名。它接收三个参数:content
是要签名的内容,pem_private_key
是PEM格式的RSA私钥字符串,hash_algorithm
是哈希算法,可以是 ‘SHA-1’ 或 ‘SHA-256’。函数内部首先调用 load_private_key
函数加载私钥,然后根据指定的哈希算法创建一个哈希对象,最后使用私钥对内容进行签名,返回签名结果。
_format_private_key(private_key)
: 这个函数用于格式化RSA私钥,确保它以 __pem_begin
开始且以 __pem_end
结束。如果私钥字符串没有以这些标志开始或结束,它会在私钥的前面或后面添加相应的标志。
# -*- coding: utf-8 -*-
# @Time : 2021-01-24 13:58
# @Author : makun 15902051493
# @FileName: GetCertSN.py
# @Describe: 获取证书序列号
import OpenSSL
import hashlib
import re
def md5(string):
print('')
return hashlib.md5(string.encode('utf-8')).hexdigest()
# 应用公钥证书序列号
def get_app_cert_cn(cert_str=None, cert_file=None):
cert_str = cert_str or open(cert_file).read()
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_str)
try:
res = cert.get_signature_algorithm()
if not re.match(b'sha.+WithRSAEncryption', res):
return None
except:
return None
cert_issue = cert.get_issuer()
op = ''
b = list(cert_issue.get_components())
for i in range(len(b)):
a = list(b[len(b) - 1 - i])
opp = "{}={}".format(a[0].decode(), a[1].decode())
op = op + opp + ','
return md5(op[:-1] + str(cert.get_serial_number()))
# 根证书序列号
def get_root_cn_sn(cert_file):
root_cert = open(cert_file).read()
cert_list = root_cert.split('-----BEGIN CERTIFICATE-----')
root_cert_sn = ''
for i in cert_list:
if not len(i):
continue
cert_sn = get_app_cert_cn('-----BEGIN CERTIFICATE-----' + i)
if cert_sn is not None:
root_cert_sn = root_cert_sn + cert_sn + '_'
return root_cert_sn[:-1]
md5(string)
: 这是一个简单的MD5哈希函数,接收一个字符串 string
作为输入,对其进行MD5哈希,并返回哈希结果的十六进制表示。
get_app_cert_cn(cert_str=None, cert_file=None)
: 这个函数用于获取应用公钥证书的序列号。它可以接收两个参数,cert_str
是证书的字符串表示,cert_file
是证书文件的路径。首先,它会加载证书并进行一些验证操作,检查证书的签名算法是否以 “sha” 开头,并以 “WithRSAEncryption” 结尾。如果验证失败,返回 None
。然后,它获取证书的颁发者信息,将其格式化为字符串,计算这个字符串的MD5哈希值,然后将MD5哈希值与证书的序列号组合在一起,返回最终的序列号信息。
get_root_cn_sn(cert_file)
: 这个函数用于获取根证书的序列号。它接收一个参数 cert_file
,是根证书文件的路径。首先,它读取根证书文件,然后将文件内容按 “-----BEGIN CERTIFICATE-----” 分割成多个证书块。接着,它通过调用 get_app_cert_cn
函数获取每个证书块的序列号,然后将这些序列号以下划线连接在一起,返回根证书的序列号字符串。
3、HttpZfbPay.py
# -*- coding: utf-8 -*-
# @Time : 2021-01-24 13:35
# @Author : makun 15902051493
# @FileName: HttpZfbPay.py
# @Describe: 使用http协议直接进行支付宝的支付操作
# https://opendocs.alipay.com/apis/api_28/alipay.fund.trans.uni.transfer
import base64
import requests, uuid, hashlib
import json
from datetime import datetime
from urllib.parse import urlencode
import RSAUtil, GetCertSN
from models import XAliPay
# '''md5 hash'''
def md5Hash(tmp):
try:
tmp = tmp.encode("utf-8")
return hashlib.md5(tmp).hexdigest()
except:
return hashlib.md5(tmp).hexdigest()
def connectQuerys(querys):
keyList = []
for each in querys:
keyList.append(each)
keyList = sorted(keyList)
tmpList = []
for i in range(0, len(keyList)):
tmpList.append("{}={}".format(keyList[i], querys.get(keyList[i])))
result = "&".join(tmpList)
return result
def getSign(querys):
tmpStr = connectQuerys(querys)
alipay_info = XAliPay.query.filter_by(id=1).first()
# TODO 请补全自己创建应用时创建的私钥
pri = '此处填写您的私钥application_private_key'
result = RSAUtil.RSASign(content=tmpStr, private_key=pri, sign_type="RSA2")
return result
def zfbRequest(zfb_name, identity, trans_amount):
import os
# 获取当前脚本的绝对路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 构建证书文件的绝对路径
root_cert_path = os.path.join(base_dir, 'alipayRootCert.crt')
app_cert_path = os.path.join(base_dir, 'appCertPublicKey_2016060801495267.crt')
alipay_info = XAliPay.query.filter_by(id=1).first()
appId = alipay_info.appid
url = "https://openapi.alipay.com/gateway.do"
# TODO 请补全自己的密钥地址
rootCertSN = GetCertSN.get_root_cn_sn(root_cert_path)
appCertSN = GetCertSN.get_app_cert_cn(
cert_file=app_cert_path)
bizContent = {
"out_biz_no": md5Hash("{}".format(uuid.uuid4()).encode("UTF-8")),
"trans_amount": trans_amount, # 这里是金额
"product_code": "TRANS_ACCOUNT_NO_PWD",
"biz_scene": "DIRECT_TRANSFER",
"payee_info": {
# "identity": "18140208525",
"identity": identity,
"identity_type": "ALIPAY_LOGON_ID",
# "name": "陈**",
"name": zfb_name
}
}
querys = {
'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'method': 'alipay.fund.trans.uni.transfer',
'app_id': appId,
'sign_type': 'RSA2',
'version': '1.0',
'charset': 'utf-8',
'biz_content': json.dumps(bizContent, separators=(',', ':'), ensure_ascii=False),
'alipay_root_cert_sn': rootCertSN,
'app_cert_sn': appCertSN
}
sign = getSign(querys)
sign_base64 = base64.b64encode(sign).decode('utf-8')
querys['sign'] = sign_base64
url = "{}?{}".format(url, urlencode(querys))
req = requests.post(url=url)
content = req.json()
req.close()
print(content['alipay_fund_trans_uni_transfer_response'])
return content['alipay_fund_trans_uni_transfer_response']
导入模块:代码中导入了一些需要使用的Python模块,包括 base64
、requests
、uuid
、hashlib
、json
、datetime
、urlencode
,以及自定义的模块 RSAUtil
和 GetCertSN
。
md5Hash(tmp)
: 这是一个用于计算MD5哈希的函数,接收一个字符串 tmp
作为输入,将其编码为UTF-8,然后计算其MD5哈希值并返回。
connectQuerys(querys)
: 这个函数接收一个字典 querys
,将字典中的键按字母顺序排序,然后将键值对按照格式连接成字符串并返回。
getSign(querys)
: 这个函数用于生成签名。首先,它调用 connectQuerys
函数将传入的 querys
字典连接成字符串。然后,它获取私钥并使用 RSAUtil.RSASign
函数对连接后的字符串进行RSA签名,签名算法根据 sign_type
参数来确定(支持RSA和RSA2两种算法)。最后,返回生成的签名。
zfbRequest(zfb_name, identity, trans_amount)
: 这个函数是主要的请求支付宝转账的部分。它接收三个参数:zfb_name
(支付宝账户名称)、identity
(身份标识)、trans_amount
(转账金额)。在函数内部,首先获取一些配置信息,包括应用ID、证书信息等。然后,构建了一个包含转账信息的 bizContent
字典。接着,构建了一个包含请求参数的 querys
字典,其中包括时间戳、方法、应用ID、签名类型、版本、字符集、业务内容、根证书序列号、应用证书序列号等信息。然后,调用 getSign
函数生成签名,并将签名添加到 querys
中。最后,将请求参数拼接成URL,发送POST请求到支付宝接口,并返回响应结果。
1、 修改HttpZfbPay.py 44行的 pri参数
2、修改为自己的证书
root_cert_path = os.path.join(base_dir, ‘alipayRootCert.crt’)
app_cert_path = os.path.join(base_dir, ‘appCertPublicKey_2016060801495267.crt’)
3、调用zfbRequest(zfb_name, identity, trans_amount)
传入真实姓名、支付宝账号、打款金额
zfbRequest方法返回值可能如下,各位可以参考来写后续逻辑:
{'msg': 'Business Failed', 'code': '40004', 'sub_msg': '请求金额不能低于0.1元', 'sub_code': 'EXCEED_LIMIT_SM_MIN_AMOUNT'}
{'msg': 'Business Failed', 'code': '40004', 'sub_msg': '余额不足,建议尽快充值。后续可登录电脑端支付宝,自主设置余额预警提醒功能。', 'sub_code': 'BALANCE_IS_NOT_ENOUGH'}
{'msg': 'Business Failed', 'code': '40004', 'sub_msg': '收款账号不存在或姓名有误,建议核实账号和姓名是否准确', 'sub_code': 'PAYEE_NOT_EXIST'}
{'code': '10000', 'msg': 'Success', 'order_id': '20240117020070011500500094030081', 'out_biz_no': '1d90d3bc2af0665dce63a72d90da769a', 'pay_fund_order_id': '20240117020070011500500094030081', 'status': 'SUCCESS', 'trans_date': '2024-01-17 16:02:29'}
code=='10000’就是转账成功