From 302593eed7f459e48ed50481d00b2694efd05bdb Mon Sep 17 00:00:00 2001 From: lihan Date: Wed, 5 Jun 2019 20:14:27 +0800 Subject: [PATCH 1/4] fix reload --- wechat/crypt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wechat/crypt.py b/wechat/crypt.py index 283598d..4dee3e3 100644 --- a/wechat/crypt.py +++ b/wechat/crypt.py @@ -1,4 +1,4 @@ -#encoding=utf-8 +# encoding=utf-8 import base64 import string @@ -10,6 +10,7 @@ import xml.etree.cElementTree as ET import sys import socket +from imp import reload reload(sys) sys.setdefaultencoding('utf-8') @@ -82,7 +83,7 @@ def extract(self, xmltext): xml_tree = ET.fromstring(xmltext) encrypt = xml_tree.find("Encrypt") touser_name = xml_tree.find("ToUserName") - if touser_name != None: + if touser_name is not None: touser_name = touser_name.text return WXBizMsgCrypt_OK, encrypt.text, touser_name except Exception: From 46a518af9b206c79e1169bf9463c3d7b0b7c50aa Mon Sep 17 00:00:00 2001 From: lihan Date: Wed, 5 Jun 2019 20:19:38 +0800 Subject: [PATCH 2/4] remove setdefaultencoding --- wechat/crypt.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wechat/crypt.py b/wechat/crypt.py index 4dee3e3..5aa49e8 100644 --- a/wechat/crypt.py +++ b/wechat/crypt.py @@ -8,11 +8,7 @@ import struct from Crypto.Cipher import AES import xml.etree.cElementTree as ET -import sys import socket -from imp import reload -reload(sys) -sys.setdefaultencoding('utf-8') WXBizMsgCrypt_OK = 0 WXBizMsgCrypt_ValidateSignature_Error = -40001 From 67ac09a13e752e691be3e69c0bcc11d9a1efbb07 Mon Sep 17 00:00:00 2001 From: lihan Date: Wed, 5 Jun 2019 20:36:50 +0800 Subject: [PATCH 3/4] fix simplify_send_parmas --- wechat/enterprise.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/wechat/enterprise.py b/wechat/enterprise.py index eb509ff..beefcd8 100644 --- a/wechat/enterprise.py +++ b/wechat/enterprise.py @@ -1,4 +1,4 @@ -#encoding=utf-8 +# encoding=utf-8 import requests import time @@ -60,7 +60,7 @@ def process(self, params, xml=None, token=None, corp_id=None, self.pre_process() rsp = func(self.req) self.post_process() - result = rsp.as_xml().encode('UTF-8') + result = rsp.as_xml().encode('UTF-8') if not result: return '' @@ -79,11 +79,7 @@ def format_list(data): def simplify_send_parmas(params): - keys = params.keys() - for key in keys: - if not params[key]: - del params[key] - return params + return {k: v for k, v in params.items() if v} class WxApi(WxBaseApi): From 368fe234b34f9478a912de0a1c001cf591895a05 Mon Sep 17 00:00:00 2001 From: lihan Date: Fri, 26 Jul 2019 17:00:17 +0800 Subject: [PATCH 4/4] use cryptography instead of pycrypto --- .gitignore | 1 + Makefile | 24 ++++++++++ README.md | 6 ++- setup.py | 49 +++++++++++++------ tests/test_crypto.py | 30 ++++++++++++ wechat/crypt.py | 111 ++++++++++++++++++++++--------------------- 6 files changed, 150 insertions(+), 71 deletions(-) create mode 100644 Makefile create mode 100644 tests/test_crypto.py diff --git a/.gitignore b/.gitignore index 2357a38..4c706fb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dist *.pyc *.db .idea +/venv* ### Django ### *.log diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d117b0f --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +clean_pyc: + @find . -name "*.pyc" -exec rm {} + + +new_venv: + #@virtualenv venv + @python3 -mvenv venv + +install: + @source venv/bin/activate; pip install -e . + +init: new_venv + @source venv/bin/activate; pip install --upgrade pip + $(MAKE) install + +clean: + @rm -rf dist build + +dist: clean + @source venv/bin/activate; pip install --upgrade setuptools wheel + @source venv/bin/activate; python setup.py sdist bdist_wheel + +publish: dist + @source venv/bin/activate; pip install --upgrade twine + @source venv/bin/activate; twine upload dist/* diff --git a/README.md b/README.md index b3149a0..01a85b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 微信公众号Python-SDK +![](https://img.shields.io/pypi/pyversions/wechat3) + 作者: [@jeff_kit](http://twitter.com/jeff_kit) 本SDK支持微信公众号以及企业号的上行消息及OAuth接口。本文档及SDK假设使用者已经具备微信公众号开发的基础知识,及有能力通过微信公众号、企业号的文档来查找相关的接口详情。 @@ -9,11 +11,11 @@ ### pip - pip install wechat + pip install wechat3 ### 源码安装 - git clone git@github.com:jeffkit/wechat.git + git clone https://github.com/tclh123/wechat.git cd wechat python setup.py install diff --git a/setup.py b/setup.py index c1f7d0a..28ce659 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,40 @@ #!/usr/bin/env python +from io import open from setuptools import setup, find_packages from wechat import VERSION -url="https://github.com/jeffkit/wechat" - -long_description="Wechat Python SDK" - -setup(name="wechat", - version=VERSION, - description=long_description, - maintainer="jeff kit", - maintainer_email="bbmyth@gmail.com", - url = url, - long_description=long_description, - install_requires = ['requests'], - packages=find_packages('.'), - ) - +setup( + name='wechat3', + version=VERSION, + description='Wechat Python SDK', + long_description=open("README.md", encoding='utf-8').read(), + long_description_content_type='text/markdown', + classifiers=[ + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Operating System :: POSIX :: Linux', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + author='jeff kit', + author_email='bbmyth@gmail.com', + maintainer='Harry Lee', + maintainer_email='tclh123@gmail.com', + url='https://github.com/tclh123/wechat', + keywords=['wechat', 'SDK'], + license="GPLv3", + packages=find_packages(exclude=['demo*', 'tests*']), + include_package_data=True, + zip_safe=False, + install_requires=['requests', 'cryptography'], +) diff --git a/tests/test_crypto.py b/tests/test_crypto.py new file mode 100644 index 0000000..60f3cd1 --- /dev/null +++ b/tests/test_crypto.py @@ -0,0 +1,30 @@ +from __future__ import print_function + +import os +import socket +import struct + +from wechat.crypt import Prpcrypt + + +key = os.urandom(32) +text = sReplyMsg = b'hello' +appid = m_sCorpid = b'10001' + +pc = Prpcrypt(key) + +text = pc.get_random_str() + struct.pack("I", socket.htonl(len(text))) + text + appid +print(text) +content = text[16:] +print(content) +xml_len = socket.ntohl(struct.unpack("I", content[:4])[0]) +print(xml_len) +xml_content = content[4:xml_len+4] +print(xml_content) +from_appid = content[xml_len+4:] +print(from_appid) + +ret, encrypt = pc.encrypt(sReplyMsg, m_sCorpid) + +ret, xml_content = pc.decrypt(encrypt, m_sCorpid) +print('decrypted:', xml_content) diff --git a/wechat/crypt.py b/wechat/crypt.py index 5aa49e8..89114f2 100644 --- a/wechat/crypt.py +++ b/wechat/crypt.py @@ -1,15 +1,21 @@ # encoding=utf-8 +import os import base64 -import string -import random +# import string +# import random import hashlib import time import struct -from Crypto.Cipher import AES import xml.etree.cElementTree as ET import socket +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes +) + WXBizMsgCrypt_OK = 0 WXBizMsgCrypt_ValidateSignature_Error = -40001 WXBizMsgCrypt_ParseXml_Error = -40002 @@ -24,13 +30,6 @@ WXBizMsgCrypt_GenReturnXml_Error = -40011 -""" -关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案 -请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。 -下载后,按照README中的“Installation”小节的提示进行pycrypto安装。 -""" - - class FormatException(Exception): pass @@ -103,33 +102,33 @@ def generate(self, encrypt, signature, timestamp, nonce): return resp_xml -class PKCS7Encoder(): - """提供基于PKCS7算法的加解密接口""" - block_size = 32 - - def encode(self, text): - """ 对需要加密的明文进行填充补位 - @param text: 需要进行填充补位操作的明文 - @return: 补齐明文字符串 - """ - text_length = len(text) - # 计算需要填充的位数 - amount_to_pad = self.block_size - (text_length % self.block_size) - if amount_to_pad == 0: - amount_to_pad = self.block_size - # 获得补位所用的字符 - pad = chr(amount_to_pad) - return text + pad * amount_to_pad - - def decode(self, decrypted): - """删除解密后明文的补位字符 - @param decrypted: 解密后的明文 - @return: 删除补位字符后的明文 - """ - pad = ord(decrypted[-1]) - if pad < 1 or pad > 32: - pad = 0 - return decrypted[:-pad] +# class PKCS7Encoder(): +# """提供基于PKCS7算法的加解密接口""" +# block_size = 32 +# +# def encode(self, text): +# """ 对需要加密的明文进行填充补位 +# @param text: 需要进行填充补位操作的明文 +# @return: 补齐明文字符串 +# """ +# text_length = len(text) +# # 计算需要填充的位数 +# amount_to_pad = self.block_size - (text_length % self.block_size) +# if amount_to_pad == 0: +# amount_to_pad = self.block_size +# # 获得补位所用的字符 +# pad = chr(amount_to_pad) +# return text + pad * amount_to_pad +# +# def decode(self, decrypted): +# """删除解密后明文的补位字符 +# @param decrypted: 解密后的明文 +# @return: 删除补位字符后的明文 +# """ +# pad = ord(decrypted[-1]) +# if pad < 1 or pad > 32: +# pad = 0 +# return decrypted[:-pad] class Prpcrypt(object): @@ -138,8 +137,8 @@ class Prpcrypt(object): def __init__(self, key): #self.key = base64.b64decode(key+"=") self.key = key - # 设置加解密模式为AES的CBC模式 - self.mode = AES.MODE_CBC + # # 设置加解密模式为AES的CBC模式 + # self.mode = AES.MODE_CBC def encrypt(self, text, appid): """对明文进行加密 @@ -149,13 +148,15 @@ def encrypt(self, text, appid): # 16位随机字符串添加到明文开头 text = self.get_random_str() + struct.pack( "I", socket.htonl(len(text))) + text + appid - # 使用自定义的填充方式对明文进行补位填充 - pkcs7 = PKCS7Encoder() - text = pkcs7.encode(text) # 加密 - cryptor = AES.new(self.key, self.mode, self.key[:16]) + encryptor = Cipher( + algorithms.AES(self.key), + modes.CBC(self.key[:16]), + backend=default_backend() + ).encryptor() + padder = padding.PKCS7(algorithms.AES.block_size).padder() try: - ciphertext = cryptor.encrypt(text) + ciphertext = encryptor.update(padder.update(text) + padder.finalize()) + encryptor.finalize() # 使用BASE64对加密后的字符串进行编码 return WXBizMsgCrypt_OK, base64.b64encode(ciphertext) except Exception: @@ -166,19 +167,20 @@ def decrypt(self, text, appid): @param text: 密文 @return: 删除填充补位后的明文 """ + decryptor = Cipher( + algorithms.AES(self.key), + modes.CBC(self.key[:16]), + backend=default_backend() + ).decryptor() + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() try: - cryptor = AES.new(self.key, self.mode, self.key[:16]) # 使用BASE64对密文进行解码,然后AES-CBC解密 - plain_text = cryptor.decrypt(base64.b64decode(text)) + plain_text = unpadder.update(decryptor.update(base64.b64decode(text)) + decryptor.finalize()) + unpadder.finalize() except Exception: return WXBizMsgCrypt_DecryptAES_Error, None try: - pad = ord(plain_text[-1]) - # 去掉补位字符串 - #pkcs7 = PKCS7Encoder() - #plain_text = pkcs7.encode(plain_text) # 去除16位随机字符串 - content = plain_text[16:-pad] + content = plain_text[16:] xml_len = socket.ntohl(struct.unpack("I", content[:4])[0]) xml_content = content[4:xml_len+4] from_appid = content[xml_len+4:] @@ -192,9 +194,10 @@ def get_random_str(self): """ 随机生成16位字符串 @return: 16位字符串 """ - rule = string.letters + string.digits - str = random.sample(rule, 16) - return "".join(str) + # rule = string.ascii_letters + string.digits + # s = random.sample(rule, 16) + # return "".join(s) + return os.urandom(16) class WXBizMsgCrypt(object):