From fd593a8b5d17d6d7f2b7ab25d8852a04e05b07d7 Mon Sep 17 00:00:00 2001 From: abelzhu Date: Wed, 22 Jul 2020 22:17:40 +0800 Subject: [PATCH 01/14] Update WXBizMsgCrypt.py --- callback_json/WXBizMsgCrypt.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/callback_json/WXBizMsgCrypt.py b/callback_json/WXBizMsgCrypt.py index 6ea7dd4..2f3875a 100644 --- a/callback_json/WXBizMsgCrypt.py +++ b/callback_json/WXBizMsgCrypt.py @@ -61,20 +61,21 @@ class JsonParse: # json消息模板 AES_TEXT_RESPONSE_TEMPLATE = '''{ -"Encrypt": "%(msg_encrypt)s", -"MsgSignature": "%(msg_signaturet)s", -"TimeStamp": %(timestamp)s, -"Nonce": "%(nonce)s" -}''' + "encrypt": "%(msg_encrypt)s", + "msgsignature": "%(msg_signaturet)s", + "timestamp": %(timestamp)s, + "nonce": "%(nonce)s" + }''' def extract(self, jsontext): """提取出json数据包中的加密消息 @param jsontext: 待提取的json字符串 @return: 提取出的加密消息字符串 """ + print jsontext try: json_dict = json.loads(jsontext) - return ierror.WXBizMsgCrypt_OK, json_dict['Encrypt'] + return ierror.WXBizMsgCrypt_OK, json_dict['encrypt'] except Exception,e: print e return ierror.WXBizMsgCrypt_ParseXml_Error, None @@ -87,11 +88,11 @@ def generate(self, encrypt, signature, timestamp, nonce): @return: 生成的json字符串 """ resp_dict = { - 'msg_encrypt' : encrypt, - 'msg_signaturet': signature, - 'timestamp' : timestamp, - 'nonce' : nonce, - } + 'msg_encrypt' : encrypt, + 'msg_signaturet': signature, + 'timestamp' : timestamp, + 'nonce' : nonce, + } resp_json = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict return resp_json @@ -207,13 +208,13 @@ def __init__(self,sToken,sEncodingAESKey,sReceiveId): self.m_sToken = sToken self.m_sReceiveId = sReceiveId - #验证URL + #验证URL #@param sMsgSignature: 签名串,对应URL参数的msg_signature #@param sTimeStamp: 时间戳,对应URL参数的timestamp #@param sNonce: 随机串,对应URL参数的nonce #@param sEchoStr: 随机串,对应URL参数的echostr #@param sReplyEchoStr: 解密之后的echostr,当return返回0时有效 - #@return:成功0,失败返回对应的错误码 + #@return:成功0,失败返回对应的错误码 def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr): sha1 = SHA1() @@ -225,7 +226,7 @@ def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr): pc = Prpcrypt(self.key) ret,sReplyEchoStr = pc.decrypt(sEchoStr,self.m_sReceiveId) return ret,sReplyEchoStr - + def EncryptMsg(self, sReplyMsg, sNonce, timestamp = None): #将企业回复用户的消息加密打包 #@param sReplyMsg: 企业号待回复用户的消息,json格式的字符串 @@ -271,5 +272,3 @@ def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce): pc = Prpcrypt(self.key) ret,json_content = pc.decrypt(encrypt,self.m_sReceiveId) return ret,json_content - - From dc0635f519ff4bfe15f654a1af4801a9eee9efeb Mon Sep 17 00:00:00 2001 From: abelzhu Date: Thu, 27 Aug 2020 21:28:01 +0800 Subject: [PATCH 02/14] Add files via upload --- callback_json/Sample.py | 216 +++++++++++----------- callback_json/WXBizJsonMsgCrypt.py | 275 +++++++++++++++++++++++++++++ callback_json/ierror.py | 40 ++--- 3 files changed, 403 insertions(+), 128 deletions(-) create mode 100644 callback_json/WXBizJsonMsgCrypt.py diff --git a/callback_json/Sample.py b/callback_json/Sample.py index 4e31937..ab47c01 100644 --- a/callback_json/Sample.py +++ b/callback_json/Sample.py @@ -1,108 +1,108 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################### -# Author: jonyqin -# Created Time: Thu 11 Sep 2014 03:55:41 PM CST -# File Name: Sample.py -# Description: WXBizMsgCrypt 使用demo文件 -######################################################################### -from WXBizMsgCrypt import WXBizMsgCrypt -import sys - -if __name__ == "__main__": - #假设企业在企业微信后台上设置的参数如下 - sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" - sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" - sCorpID = "ww1436e0e65a779aee" - ''' - ------------使用示例一:验证回调URL--------------- - *企业开启回调模式时,企业号会向验证url发送一个get请求 - 假设点击验证时,企业收到类似请求: - * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D - * HTTP/1.1 Host: qy.weixin.qq.com - - 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), - 这一步注意作URL解码。 - 2.验证消息体签名的正确性 - 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 - 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 - ''' - wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID) - sVerifyMsgSig="012bc692d0a58dd4b10f8dfe5c4ac00ae211ebeb" - sVerifyTimeStamp="1476416373" - sVerifyNonce="47744683" - sVerifyEchoStr="fsi1xnbH4yQh0+PJxcOdhhK6TDXkjMyhEPA7xB2TGz6b+g7xyAbEkRxN/3cNXW9qdqjnoVzEtpbhnFyq6SVHyA==" - ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr) - if(ret!=0): - print "ERR: VerifyURL ret: " + str(ret) - sys.exit(1) - else: - print "done VerifyURL" - #验证URL成功,将sEchoStr返回给企业号 - - print "==============================" - ''' - ------------使用示例二:对用户回复的消息解密--------------- - 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 - 假设企业收到企业微信的回调消息如下: - POST /cgi-bin/wxpush? msg_signature=e3647471e395139e2308c1fa963f2d648a00b90e×tamp=1409659813&nonce=1372623149 HTTP/1.1 - Host: qy.weixin.qq.com - - { - "ToUserName": "wx5823bf96d3bd56c7", - "Encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", - "AgentID": 218 - } - - 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) - 2.验证消息体签名的正确性。 3.将post请求的数据进行json解析,并将标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 - 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 - ''' - - sReqNonce = "1372623149" - sReqTimeStamp = "1409659813" - - sReqMsgSig = "e3647471e395139e2308c1fa963f2d648a00b90e" - sReqData = '{ "ToUserName": "wx5823bf96d3bd56c7", "Encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", "AgentID": 218 }'; - ret,sMsg=wxcpt.DecryptMsg( sReqData, sReqMsgSig, sReqTimeStamp, sReqNonce) - if( ret!=0 ): - print "ERR: DecryptMsg ret: " + str(ret) - sys.exit(1) - else: - print sMsg - # 解密成功,sMsg即为json格式的明文 - # TODO: 对明文的处理 - # ... - # ... - - print "==============================" - - ''' - ------------使用示例三:企业回复用户消息的加密--------------- - 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。 - 假设企业需要回复用户的明文如下: - - { - "ToUserName": "mycreate", - "FromUserName":"wx5823bf96d3bd56c7", - "CreateTime": 1348831860, - "MsgType": "text", - "Content": "this is a test", - "MsgId": 1234567890123456, - "AgentID": 128 - } - - 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 - 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业号。 - 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 - ''' - #sRespData = ' { "ToUserName": "mycreate", "FromUserName":"wx5823bf96d3bd56c7", "CreateTime": 1348831860, "MsgType": "text", "Content": "this is a test", "MsgId": 1234567890123456, "AgentID": 128 }'; - sRespData = '{ "ToUserName": "wx5823bf96d3bd56c7", "FromUserName": :mycreate", "CreateTime": 1409659813, "MsgType": "text", "Content": "hello", "MsgId": 4561255354251345929, "AgentID": 218}' - ret,sEncryptMsg=wxcpt.EncryptMsg(sRespData, sReqNonce, sReqTimeStamp) - if( ret!=0 ): - print "ERR: EncryptMsg ret: " + str(ret) - sys.exit(1) - else: - print sEncryptMsg - #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 - print "==============================" +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 03:55:41 PM CST +# File Name: Sample.py +# Description: WXBizJsonMsgCrypt 使用demo文件 +######################################################################### +from WXBizJsonMsgCrypt import WXBizJsonMsgCrypt +import sys + +if __name__ == "__main__": + #假设企业在企业微信后台上设置的参数如下 + sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" + sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + sCorpID = "ww1436e0e65a779aee" + ''' + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业号会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + ''' + wxcpt=WXBizJsonMsgCrypt(sToken,sEncodingAESKey,sCorpID) + sVerifyMsgSig="012bc692d0a58dd4b10f8dfe5c4ac00ae211ebeb" + sVerifyTimeStamp="1476416373" + sVerifyNonce="47744683" + sVerifyEchoStr="fsi1xnbH4yQh0+PJxcOdhhK6TDXkjMyhEPA7xB2TGz6b+g7xyAbEkRxN/3cNXW9qdqjnoVzEtpbhnFyq6SVHyA==" + ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr) + if(ret!=0): + print "ERR: VerifyURL ret: " + str(ret) + sys.exit(1) + else: + print "done VerifyURL" + #验证URL成功,将sEchoStr返回给企业号 + + print "==============================" + ''' + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=e3647471e395139e2308c1fa963f2d648a00b90e×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + + { + "tousername": "wx5823bf96d3bd56c7", + "encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", + "agentid": 218 + } + + 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 3.将post请求的数据进行json解析,并将"encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + ''' + + sReqNonce = "1372623149" + sReqTimeStamp = "1409659813" + + sReqMsgSig = "e3647471e395139e2308c1fa963f2d648a00b90e" + sReqData = '{ "tousername": "wx5823bf96d3bd56c7", "encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", "agentid": 218 }'; + ret,sMsg=wxcpt.DecryptMsg( sReqData, sReqMsgSig, sReqTimeStamp, sReqNonce) + if( ret!=0 ): + print "ERR: DecryptMsg ret: " + str(ret) + sys.exit(1) + else: + print sMsg + # 解密成功,sMsg即为json格式的明文 + # TODO: 对明文的处理 + # ... + # ... + + print "==============================" + + ''' + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。 + 假设企业需要回复用户的明文如下: + + { + "ToUserName": "mycreate", + "FromUserName":"wx5823bf96d3bd56c7", + "CreateTime": 1348831860, + "MsgType": "text", + "Content": "this is a test", + "MsgId": 1234567890123456, + "AgentID": 128 + } + + 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业号。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + ''' + #sRespData = ' { "ToUserName": "mycreate", "FromUserName":"wx5823bf96d3bd56c7", "CreateTime": 1348831860, "MsgType": "text", "Content": "this is a test", "MsgId": 1234567890123456, "AgentID": 128 }'; + sRespData = '{ "ToUserName": "wx5823bf96d3bd56c7", "FromUserName": :mycreate", "CreateTime": 1409659813, "MsgType": "text", "Content": "hello", "MsgId": 4561255354251345929, "AgentID": 218}' + ret,sEncryptMsg=wxcpt.EncryptMsg(sRespData, sReqNonce, sReqTimeStamp) + if( ret!=0 ): + print "ERR: EncryptMsg ret: " + str(ret) + sys.exit(1) + else: + print sEncryptMsg + #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 + print "==============================" diff --git a/callback_json/WXBizJsonMsgCrypt.py b/callback_json/WXBizJsonMsgCrypt.py new file mode 100644 index 0000000..d0be3cc --- /dev/null +++ b/callback_json/WXBizJsonMsgCrypt.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +#-*- encoding:utf-8 -*- + +""" 对企业微信发送给企业后台的消息加解密示例代码. +@copyright: Copyright (c) 1998-2020 Tencent Inc. + +""" +# ------------------------------------------------------------------------ + +import base64 +import string +import random +import hashlib +import time +import struct +from Crypto.Cipher import AES +import sys +import socket +import json + +reload(sys) +import ierror +sys.setdefaultencoding('utf-8') + +""" +关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案 +请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。 +下载后,按照README中的“Installation”小节的提示进行pycrypto安装。 +""" +class FormatException(Exception): + pass + +def throw_exception(message, exception_class=FormatException): + """my define raise exception function""" + raise exception_class(message) + +class SHA1: + """计算企业微信的消息签名接口""" + + def getSHA1(self, token, timestamp, nonce, encrypt): + """用SHA1算法生成安全签名 + @param token: 票据 + @param timestamp: 时间戳 + @param encrypt: 密文 + @param nonce: 随机字符串 + @return: 安全签名 + """ + try: + sortlist = [token, timestamp, nonce, encrypt] + sortlist.sort() + sha = hashlib.sha1() + sha.update("".join(sortlist)) + return ierror.WXBizMsgCrypt_OK, sha.hexdigest() + except Exception,e: + print e + return ierror.WXBizMsgCrypt_ComputeSignature_Error, None + + +class JsonParse: + """提供提取消息格式中的密文及生成回复消息格式的接口""" + + # json消息模板 + AES_TEXT_RESPONSE_TEMPLATE = '''{ + "encrypt": "%(msg_encrypt)s", + "msgsignature": "%(msg_signaturet)s", + "timestamp": "%(timestamp)s", + "nonce": "%(nonce)s" + }''' + + def extract(self, jsontext): + """提取出json数据包中的加密消息 + @param jsontext: 待提取的json字符串 + @return: 提取出的加密消息字符串 + """ + try: + json_dict = json.loads(jsontext) + return ierror.WXBizMsgCrypt_OK, json_dict['encrypt'] + except Exception,e: + print e + return ierror.WXBizMsgCrypt_ParseJson_Error, None + def generate(self, encrypt, signature, timestamp, nonce): + """生成json消息 + @param encrypt: 加密后的消息密文 + @param signature: 安全签名 + @param timestamp: 时间戳 + @param nonce: 随机字符串 + @return: 生成的json字符串 + """ + resp_dict = { + 'msg_encrypt' : encrypt, + 'msg_signaturet': signature, + 'timestamp' : timestamp, + 'nonce' : nonce, + } + resp_json = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict + return resp_json + + +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): + """提供接收和推送给企业微信消息的加解密接口""" + + def __init__(self,key): + + #self.key = base64.b64decode(key+"=") + self.key = key + # 设置加解密模式为AES的CBC模式 + self.mode = AES.MODE_CBC + + + def encrypt(self,text,receiveid): + """对明文进行加密 + @param text: 需要加密的明文 + @return: 加密得到的字符串 + """ + # 16位随机字符串添加到明文开头 + text = self.get_random_str() + struct.pack("I",socket.htonl(len(text))) + text + receiveid + # 使用自定义的填充方式对明文进行补位填充 + pkcs7 = PKCS7Encoder() + text = pkcs7.encode(text) + # 加密 + cryptor = AES.new(self.key,self.mode,self.key[:16]) + try: + ciphertext = cryptor.encrypt(text) + # 使用BASE64对加密后的字符串进行编码 + return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext) + except Exception,e: + print e + return ierror.WXBizMsgCrypt_EncryptAES_Error,None + + def decrypt(self,text,receiveid): + """对解密后的明文进行补位删除 + @param text: 密文 + @return: 删除填充补位后的明文 + """ + try: + cryptor = AES.new(self.key,self.mode,self.key[:16]) + # 使用BASE64对密文进行解码,然后AES-CBC解密 + plain_text = cryptor.decrypt(base64.b64decode(text)) + except Exception,e: + print e + return ierror.WXBizMsgCrypt_DecryptAES_Error,None + try: + pad = ord(plain_text[-1]) + # 去掉补位字符串 + #pkcs7 = PKCS7Encoder() + #plain_text = pkcs7.encode(plain_text) + # 去除16位随机字符串 + content = plain_text[16:-pad] + json_len = socket.ntohl(struct.unpack("I",content[ : 4])[0]) + json_content = content[4 : json_len+4] + from_receiveid = content[json_len+4:] + except Exception,e: + print e + return ierror.WXBizMsgCrypt_IllegalBuffer,None + if from_receiveid != receiveid: + print "receiveid not match" + print from_receiveid + return ierror.WXBizMsgCrypt_ValidateCorpid_Error,None + return 0,json_content + + def get_random_str(self): + """ 随机生成16位字符串 + @return: 16位字符串 + """ + rule = string.letters + string.digits + str = random.sample(rule, 16) + return "".join(str) + +class WXBizJsonMsgCrypt(object): + #构造函数 + def __init__(self,sToken,sEncodingAESKey,sReceiveId): + try: + self.key = base64.b64decode(sEncodingAESKey+"=") + assert len(self.key) == 32 + except: + throw_exception("[error]: EncodingAESKey unvalid !", FormatException) + # return ierror.WXBizMsgCrypt_IllegalAesKey,None + self.m_sToken = sToken + self.m_sReceiveId = sReceiveId + + #验证URL + #@param sMsgSignature: 签名串,对应URL参数的msg_signature + #@param sTimeStamp: 时间戳,对应URL参数的timestamp + #@param sNonce: 随机串,对应URL参数的nonce + #@param sEchoStr: 随机串,对应URL参数的echostr + #@param sReplyEchoStr: 解密之后的echostr,当return返回0时有效 + #@return:成功0,失败返回对应的错误码 + + def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr): + sha1 = SHA1() + ret,signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr) + if ret != 0: + return ret, None + if not signature == sMsgSignature: + return ierror.WXBizMsgCrypt_ValidateSignature_Error, None + pc = Prpcrypt(self.key) + ret,sReplyEchoStr = pc.decrypt(sEchoStr,self.m_sReceiveId) + return ret,sReplyEchoStr + + def EncryptMsg(self, sReplyMsg, sNonce, timestamp = None): + #将企业回复用户的消息加密打包 + #@param sReplyMsg: 企业号待回复用户的消息,json格式的字符串 + #@param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间 + #@param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce + #sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串, + #return:成功0,sEncryptMsg,失败返回对应的错误码None + pc = Prpcrypt(self.key) + ret,encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId) + if ret != 0: + return ret,None + if timestamp is None: + timestamp = str(int(time.time())) + # 生成安全签名 + sha1 = SHA1() + ret,signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt) + if ret != 0: + return ret,None + jsonParse = JsonParse() + return ret,jsonParse.generate(encrypt, signature, timestamp, sNonce) + + def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce): + # 检验消息的真实性,并且获取解密后的明文 + # @param sMsgSignature: 签名串,对应URL参数的msg_signature + # @param sTimeStamp: 时间戳,对应URL参数的timestamp + # @param sNonce: 随机串,对应URL参数的nonce + # @param sPostData: 密文,对应POST请求的数据 + # json_content: 解密后的原文,当return返回0时有效 + # @return: 成功0,失败返回对应的错误码 + # 验证安全签名 + jsonParse = JsonParse() + ret,encrypt = jsonParse.extract(sPostData) + if ret != 0: + return ret, None + sha1 = SHA1() + ret,signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt) + if ret != 0: + return ret, None + if not signature == sMsgSignature: + print "signature not match" + print signature + return ierror.WXBizMsgCrypt_ValidateSignature_Error, None + pc = Prpcrypt(self.key) + ret,json_content = pc.decrypt(encrypt,self.m_sReceiveId) + return ret,json_content + + diff --git a/callback_json/ierror.py b/callback_json/ierror.py index 6678fec..51c02c3 100644 --- a/callback_json/ierror.py +++ b/callback_json/ierror.py @@ -1,20 +1,20 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################### -# Author: jonyqin -# Created Time: Thu 11 Sep 2014 01:53:58 PM CST -# File Name: ierror.py -# Description:定义错误码含义 -######################################################################### -WXBizMsgCrypt_OK = 0 -WXBizMsgCrypt_ValidateSignature_Error = -40001 -WXBizMsgCrypt_ParseXml_Error = -40002 -WXBizMsgCrypt_ComputeSignature_Error = -40003 -WXBizMsgCrypt_IllegalAesKey = -40004 -WXBizMsgCrypt_ValidateCorpid_Error = -40005 -WXBizMsgCrypt_EncryptAES_Error = -40006 -WXBizMsgCrypt_DecryptAES_Error = -40007 -WXBizMsgCrypt_IllegalBuffer = -40008 -WXBizMsgCrypt_EncodeBase64_Error = -40009 -WXBizMsgCrypt_DecodeBase64_Error = -40010 -WXBizMsgCrypt_GenReturnXml_Error = -40011 +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 01:53:58 PM CST +# File Name: ierror.py +# Description:定义错误码含义 +######################################################################### +WXBizMsgCrypt_OK = 0 +WXBizMsgCrypt_ValidateSignature_Error = -40001 +WXBizMsgCrypt_ParseJson_Error = -40002 +WXBizMsgCrypt_ComputeSignature_Error = -40003 +WXBizMsgCrypt_IllegalAesKey = -40004 +WXBizMsgCrypt_ValidateCorpid_Error = -40005 +WXBizMsgCrypt_EncryptAES_Error = -40006 +WXBizMsgCrypt_DecryptAES_Error = -40007 +WXBizMsgCrypt_IllegalBuffer = -40008 +WXBizMsgCrypt_EncodeBase64_Error = -40009 +WXBizMsgCrypt_DecodeBase64_Error = -40010 +WXBizMsgCrypt_GenReturnJson_Error = -40011 From 2d3dd0c9a13fdf4f3b38cc68ee995d38677c0cc3 Mon Sep 17 00:00:00 2001 From: abelzhu Date: Thu, 27 Aug 2020 21:28:53 +0800 Subject: [PATCH 03/14] Delete WXBizMsgCrypt.py --- callback_json/WXBizMsgCrypt.py | 274 --------------------------------- 1 file changed, 274 deletions(-) delete mode 100644 callback_json/WXBizMsgCrypt.py diff --git a/callback_json/WXBizMsgCrypt.py b/callback_json/WXBizMsgCrypt.py deleted file mode 100644 index 2f3875a..0000000 --- a/callback_json/WXBizMsgCrypt.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/env python -#-*- encoding:utf-8 -*- - -""" 对企业微信发送给企业后台的消息加解密示例代码. -@copyright: Copyright (c) 1998-2014 Tencent Inc. - -""" -# ------------------------------------------------------------------------ - -import base64 -import string -import random -import hashlib -import time -import struct -from Crypto.Cipher import AES -import sys -import socket -import json - -reload(sys) -import ierror -sys.setdefaultencoding('utf-8') - -""" -关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案 -请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。 -下载后,按照README中的“Installation”小节的提示进行pycrypto安装。 -""" -class FormatException(Exception): - pass - -def throw_exception(message, exception_class=FormatException): - """my define raise exception function""" - raise exception_class(message) - -class SHA1: - """计算企业微信的消息签名接口""" - - def getSHA1(self, token, timestamp, nonce, encrypt): - """用SHA1算法生成安全签名 - @param token: 票据 - @param timestamp: 时间戳 - @param encrypt: 密文 - @param nonce: 随机字符串 - @return: 安全签名 - """ - try: - sortlist = [token, timestamp, nonce, encrypt] - sortlist.sort() - sha = hashlib.sha1() - sha.update("".join(sortlist)) - return ierror.WXBizMsgCrypt_OK, sha.hexdigest() - except Exception,e: - print e - return ierror.WXBizMsgCrypt_ComputeSignature_Error, None - - -class JsonParse: - """提供提取消息格式中的密文及生成回复消息格式的接口""" - - # json消息模板 - AES_TEXT_RESPONSE_TEMPLATE = '''{ - "encrypt": "%(msg_encrypt)s", - "msgsignature": "%(msg_signaturet)s", - "timestamp": %(timestamp)s, - "nonce": "%(nonce)s" - }''' - - def extract(self, jsontext): - """提取出json数据包中的加密消息 - @param jsontext: 待提取的json字符串 - @return: 提取出的加密消息字符串 - """ - print jsontext - try: - json_dict = json.loads(jsontext) - return ierror.WXBizMsgCrypt_OK, json_dict['encrypt'] - except Exception,e: - print e - return ierror.WXBizMsgCrypt_ParseXml_Error, None - def generate(self, encrypt, signature, timestamp, nonce): - """生成json消息 - @param encrypt: 加密后的消息密文 - @param signature: 安全签名 - @param timestamp: 时间戳 - @param nonce: 随机字符串 - @return: 生成的json字符串 - """ - resp_dict = { - 'msg_encrypt' : encrypt, - 'msg_signaturet': signature, - 'timestamp' : timestamp, - 'nonce' : nonce, - } - resp_json = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict - return resp_json - - -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): - """提供接收和推送给企业微信消息的加解密接口""" - - def __init__(self,key): - - #self.key = base64.b64decode(key+"=") - self.key = key - # 设置加解密模式为AES的CBC模式 - self.mode = AES.MODE_CBC - - - def encrypt(self,text,receiveid): - """对明文进行加密 - @param text: 需要加密的明文 - @return: 加密得到的字符串 - """ - # 16位随机字符串添加到明文开头 - text = self.get_random_str() + struct.pack("I",socket.htonl(len(text))) + text + receiveid - # 使用自定义的填充方式对明文进行补位填充 - pkcs7 = PKCS7Encoder() - text = pkcs7.encode(text) - # 加密 - cryptor = AES.new(self.key,self.mode,self.key[:16]) - try: - ciphertext = cryptor.encrypt(text) - # 使用BASE64对加密后的字符串进行编码 - return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext) - except Exception,e: - print e - return ierror.WXBizMsgCrypt_EncryptAES_Error,None - - def decrypt(self,text,receiveid): - """对解密后的明文进行补位删除 - @param text: 密文 - @return: 删除填充补位后的明文 - """ - try: - cryptor = AES.new(self.key,self.mode,self.key[:16]) - # 使用BASE64对密文进行解码,然后AES-CBC解密 - plain_text = cryptor.decrypt(base64.b64decode(text)) - except Exception,e: - print e - return ierror.WXBizMsgCrypt_DecryptAES_Error,None - try: - pad = ord(plain_text[-1]) - # 去掉补位字符串 - #pkcs7 = PKCS7Encoder() - #plain_text = pkcs7.encode(plain_text) - # 去除16位随机字符串 - content = plain_text[16:-pad] - json_len = socket.ntohl(struct.unpack("I",content[ : 4])[0]) - json_content = content[4 : json_len+4] - from_receiveid = content[json_len+4:] - except Exception,e: - print e - return ierror.WXBizMsgCrypt_IllegalBuffer,None - if from_receiveid != receiveid: - print "receiveid not match" - print from_receiveid - return ierror.WXBizMsgCrypt_ValidateCorpid_Error,None - return 0,json_content - - def get_random_str(self): - """ 随机生成16位字符串 - @return: 16位字符串 - """ - rule = string.letters + string.digits - str = random.sample(rule, 16) - return "".join(str) - -class WXBizMsgCrypt(object): - #构造函数 - def __init__(self,sToken,sEncodingAESKey,sReceiveId): - try: - self.key = base64.b64decode(sEncodingAESKey+"=") - assert len(self.key) == 32 - except: - throw_exception("[error]: EncodingAESKey unvalid !", FormatException) - # return ierror.WXBizMsgCrypt_IllegalAesKey,None - self.m_sToken = sToken - self.m_sReceiveId = sReceiveId - - #验证URL - #@param sMsgSignature: 签名串,对应URL参数的msg_signature - #@param sTimeStamp: 时间戳,对应URL参数的timestamp - #@param sNonce: 随机串,对应URL参数的nonce - #@param sEchoStr: 随机串,对应URL参数的echostr - #@param sReplyEchoStr: 解密之后的echostr,当return返回0时有效 - #@return:成功0,失败返回对应的错误码 - - def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr): - sha1 = SHA1() - ret,signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr) - if ret != 0: - return ret, None - if not signature == sMsgSignature: - return ierror.WXBizMsgCrypt_ValidateSignature_Error, None - pc = Prpcrypt(self.key) - ret,sReplyEchoStr = pc.decrypt(sEchoStr,self.m_sReceiveId) - return ret,sReplyEchoStr - - def EncryptMsg(self, sReplyMsg, sNonce, timestamp = None): - #将企业回复用户的消息加密打包 - #@param sReplyMsg: 企业号待回复用户的消息,json格式的字符串 - #@param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间 - #@param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce - #sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串, - #return:成功0,sEncryptMsg,失败返回对应的错误码None - pc = Prpcrypt(self.key) - ret,encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId) - if ret != 0: - return ret,None - if timestamp is None: - timestamp = str(int(time.time())) - # 生成安全签名 - sha1 = SHA1() - ret,signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt) - if ret != 0: - return ret,None - jsonParse = JsonParse() - return ret,jsonParse.generate(encrypt, signature, timestamp, sNonce) - - def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce): - # 检验消息的真实性,并且获取解密后的明文 - # @param sMsgSignature: 签名串,对应URL参数的msg_signature - # @param sTimeStamp: 时间戳,对应URL参数的timestamp - # @param sNonce: 随机串,对应URL参数的nonce - # @param sPostData: 密文,对应POST请求的数据 - # json_content: 解密后的原文,当return返回0时有效 - # @return: 成功0,失败返回对应的错误码 - # 验证安全签名 - jsonParse = JsonParse() - ret,encrypt = jsonParse.extract(sPostData) - if ret != 0: - return ret, None - sha1 = SHA1() - ret,signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt) - if ret != 0: - return ret, None - if not signature == sMsgSignature: - print "signature not match" - print signature - return ierror.WXBizMsgCrypt_ValidateSignature_Error, None - pc = Prpcrypt(self.key) - ret,json_content = pc.decrypt(encrypt,self.m_sReceiveId) - return ret,json_content From aa41bb308a6c840602fea49a3509f39b0be3b086 Mon Sep 17 00:00:00 2001 From: abelzhu Date: Thu, 26 Nov 2020 14:47:47 +0800 Subject: [PATCH 04/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 492f2da..f0bf688 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ golang : https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail. 详细使用方法参考examples路径下的测试用例 # 关于token的缓存 -token是需要缓存的,不能每次调用都去获取token,[否者会中频率限制](https://work.weixin.qq.com/api/doc#10013/%E7%AC%AC%E5%9B%9B%E6%AD%A5%EF%BC%9A%E7%BC%93%E5%AD%98%E5%92%8C%E5%88%B7%E6%96%B0access_token) +token是需要缓存的,不能每次调用都去获取token,[否则会中频率限制](https://work.weixin.qq.com/api/doc#10013/%E7%AC%AC%E5%9B%9B%E6%AD%A5%EF%BC%9A%E7%BC%93%E5%AD%98%E5%92%8C%E5%88%B7%E6%96%B0access_token) 在本库的设计里,token是以类里的一个变量缓存的 比如api/src/CorpApi.py 里的access_token变量 在类的生命周期里,这个accessToken都是存在的, 当且仅当发现token过期,CorpAPI类会自动刷新token From 3dfbc26de61ccb22d0925244417ad623d76a8afb Mon Sep 17 00:00:00 2001 From: sbzhu Date: Tue, 15 Apr 2025 14:17:26 +0800 Subject: [PATCH 05/14] Update Readme.txt --- callback/Readme.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/callback/Readme.txt b/callback/Readme.txt index 79ebb21..7b8dd0c 100644 --- a/callback/Readme.txt +++ b/callback/Readme.txt @@ -1,5 +1,5 @@ -ע -1.WXBizMsgCrypt.pyļװWXBizMsgCryptӿ࣬ṩûҵ΢ŵӿڣSample.pyļṩʹӿڵʾierror.pyṩ˴롣 -2.WXBizMsgCryptװVerifyURL, DecryptMsg, EncryptMsgӿڣֱڿ֤صurlյûظϢĽԼ߻ظϢļ̡ܹʹ÷ԲοSample.pyļ -3.ӽЭοҵ΢Źٷĵ -4.õpycrypto⣬뿪аװ˿ʹá \ No newline at end of file +注意事项 +1.WXBizMsgCrypt.py文件封装了WXBizMsgCrypt接口类(Python3以及以上版本使用 WXBizMsgCrypt3.py),提供了用户接入企业微信的三个接口,Sample.py文件提供了如何使用这三个接口的示例,ierror.py提供了错误码。 +2.WXBizMsgCrypt封装了VerifyURL, DecryptMsg, EncryptMsg三个接口,分别用于开发者验证回调url,收到用户回复消息的解密以及开发者回复消息的加密过程。使用方法可以参考Sample.py文件。 +3.加解密协议请参考企业微信官方文档。 +4.本代码用到了pycrypto第三方库,请开发者自行安装此库再使用。 From db49731830885091da117a0a16656d42b91e3dde Mon Sep 17 00:00:00 2001 From: abelzhu Date: Thu, 26 Jun 2025 13:15:48 +0800 Subject: [PATCH 06/14] =?UTF-8?q?=E6=94=AF=E6=8C=81pythonb3,=20=E4=BB=A5?= =?UTF-8?q?=E5=8F=8APython3=E7=9A=84json=E5=8A=A0=E8=A7=A3=E5=AF=86?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +- callback_json_python3/Readme.txt | 5 + callback_json_python3/Sample.py | 108 +++++++ callback_json_python3/WXBizJsonMsgCrypt.py | 287 ++++++++++++++++++ callback_json_python3/ierror.py | 20 ++ callback_python3/Readme.txt | 5 + callback_python3/Sample.py | 109 +++++++ .../WXBizMsgCrypt.py | 0 callback_python3/ierror.py | 20 ++ 9 files changed, 560 insertions(+), 3 deletions(-) create mode 100644 callback_json_python3/Readme.txt create mode 100644 callback_json_python3/Sample.py create mode 100644 callback_json_python3/WXBizJsonMsgCrypt.py create mode 100644 callback_json_python3/ierror.py create mode 100644 callback_python3/Readme.txt create mode 100644 callback_python3/Sample.py rename callback/WXBizMsgCrypt3.py => callback_python3/WXBizMsgCrypt.py (100%) create mode 100644 callback_python3/ierror.py diff --git a/README.md b/README.md index f0bf688..140e9fe 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,16 @@ golang : https://github.com/sbzhu/weworkapi_golang ryanjelin@tencent.com(企业 golang : https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail.com(个人开发者) # Director - ├── api // API 接口 │   ├── examples // API接口的测试用例 │   ├── README.md │   └── src // API接口的关键逻辑 -├── conf.py -├── README.md +├── callback // 加解密库(python2, xml格式) +├── callback_json // 加解密库(Python2, json格式, 仅适用于企业机器人/智能机器人) +├── callback_json_python3 // 加解密库(Python3, json格式, 仅适用于企业机器人/智能机器人) +├── callback_python3 // 加解密库(python2, xml格式) +├── conf.py +└── README.md # Usage 将本项目下载到你的目录,既可直接引用相关文件   diff --git a/callback_json_python3/Readme.txt b/callback_json_python3/Readme.txt new file mode 100644 index 0000000..79ebb21 --- /dev/null +++ b/callback_json_python3/Readme.txt @@ -0,0 +1,5 @@ +ע +1.WXBizMsgCrypt.pyļװWXBizMsgCryptӿ࣬ṩûҵ΢ŵӿڣSample.pyļṩʹӿڵʾierror.pyṩ˴롣 +2.WXBizMsgCryptװVerifyURL, DecryptMsg, EncryptMsgӿڣֱڿ֤صurlյûظϢĽԼ߻ظϢļ̡ܹʹ÷ԲοSample.pyļ +3.ӽЭοҵ΢Źٷĵ +4.õpycrypto⣬뿪аװ˿ʹá \ No newline at end of file diff --git a/callback_json_python3/Sample.py b/callback_json_python3/Sample.py new file mode 100644 index 0000000..740de23 --- /dev/null +++ b/callback_json_python3/Sample.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 03:55:41 PM CST +# File Name: Sample.py +# Description: WXBizJsonMsgCrypt 使用demo文件 +######################################################################### +from WXBizJsonMsgCrypt import WXBizJsonMsgCrypt +import sys + +if __name__ == "__main__": + #假设企业在企业微信后台上设置的参数如下 + sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" + sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + sCorpID = "ww1436e0e65a779aee" + ''' + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业号会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + ''' + wxcpt=WXBizJsonMsgCrypt(sToken,sEncodingAESKey,sCorpID) + sVerifyMsgSig="012bc692d0a58dd4b10f8dfe5c4ac00ae211ebeb" + sVerifyTimeStamp="1476416373" + sVerifyNonce="47744683" + sVerifyEchoStr="fsi1xnbH4yQh0+PJxcOdhhK6TDXkjMyhEPA7xB2TGz6b+g7xyAbEkRxN/3cNXW9qdqjnoVzEtpbhnFyq6SVHyA==" + ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr) + if(ret!=0): + print("ERR: VerifyURL ret: " + str(ret)) + sys.exit(1) + else: + print("done VerifyURL") + #验证URL成功,将sEchoStr返回给企业号 + + print("==============================") + ''' + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=e3647471e395139e2308c1fa963f2d648a00b90e×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + + { + "tousername": "wx5823bf96d3bd56c7", + "encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", + "agentid": 218 + } + + 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 3.将post请求的数据进行json解析,并将"encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + ''' + + sReqNonce = "1372623149" + sReqTimeStamp = "1409659813" + + sReqMsgSig = "e3647471e395139e2308c1fa963f2d648a00b90e" + sReqData = '{ "tousername": "wx5823bf96d3bd56c7", "encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", "agentid": 218 }'; + ret,sMsg=wxcpt.DecryptMsg( sReqData, sReqMsgSig, sReqTimeStamp, sReqNonce) + if( ret!=0 ): + print("ERR: DecryptMsg ret: " + str(ret)) + sys.exit(1) + else: + print(sMsg) + # 解密成功,sMsg即为json格式的明文 + # TODO: 对明文的处理 + # ... + # ... + + print("==============================") + + ''' + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。 + 假设企业需要回复用户的明文如下: + + { + "ToUserName": "mycreate", + "FromUserName":"wx5823bf96d3bd56c7", + "CreateTime": 1348831860, + "MsgType": "text", + "Content": "this is a test", + "MsgId": 1234567890123456, + "AgentID": 128 + } + + 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业号。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + ''' + #sRespData = ' { "ToUserName": "mycreate", "FromUserName":"wx5823bf96d3bd56c7", "CreateTime": 1348831860, "MsgType": "text", "Content": "this is a test", "MsgId": 1234567890123456, "AgentID": 128 }'; + sRespData = '{ "ToUserName": "wx5823bf96d3bd56c7", "FromUserName": :mycreate", "CreateTime": 1409659813, "MsgType": "text", "Content": "hello", "MsgId": 4561255354251345929, "AgentID": 218}' + ret,sEncryptMsg=wxcpt.EncryptMsg(sRespData, sReqNonce, sReqTimeStamp) + if( ret!=0 ): + print("ERR: EncryptMsg ret: " + str(ret)) + sys.exit(1) + else: + print(sEncryptMsg) + #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 + print("==============================") diff --git a/callback_json_python3/WXBizJsonMsgCrypt.py b/callback_json_python3/WXBizJsonMsgCrypt.py new file mode 100644 index 0000000..b3039fb --- /dev/null +++ b/callback_json_python3/WXBizJsonMsgCrypt.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python +#-*- encoding:utf-8 -*- + +""" 对企业微信发送给企业后台的消息加解密示例代码. +@copyright: Copyright (c) 1998-2020 Tencent Inc. + +""" +# ------------------------------------------------------------------------ + +import base64 +import string +import random +import hashlib +import time +import struct +from Crypto.Cipher import AES +import sys +import socket +import json + +import ierror + +""" +关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案 +请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。 +下载后,按照README中的“Installation”小节的提示进行pycrypto安装。 +""" +class FormatException(Exception): + pass + +def throw_exception(message, exception_class=FormatException): + """my define raise exception function""" + raise exception_class(message) + +class SHA1: + """计算企业微信的消息签名接口""" + + def getSHA1(self, token, timestamp, nonce, encrypt): + """用SHA1算法生成安全签名 + @param token: 票据 + @param timestamp: 时间戳 + @param encrypt: 密文 + @param nonce: 随机字符串 + @return: 安全签名 + """ + try: + # 确保所有输入都是字符串类型 + if isinstance(encrypt, bytes): + encrypt = encrypt.decode('utf-8') + + sortlist = [str(token), str(timestamp), str(nonce), str(encrypt)] + sortlist.sort() + sha = hashlib.sha1() + sha.update("".join(sortlist).encode('utf-8')) + return ierror.WXBizMsgCrypt_OK, sha.hexdigest() + + except Exception as e: + print(e) + return ierror.WXBizMsgCrypt_ComputeSignature_Error, None + + +class JsonParse: + """提供提取消息格式中的密文及生成回复消息格式的接口""" + + # json消息模板 + AES_TEXT_RESPONSE_TEMPLATE = '''{ + "encrypt": "%(msg_encrypt)s", + "msgsignature": "%(msg_signaturet)s", + "timestamp": "%(timestamp)s", + "nonce": "%(nonce)s" + }''' + + def extract(self, jsontext): + """提取出json数据包中的加密消息 + @param jsontext: 待提取的json字符串 + @return: 提取出的加密消息字符串 + """ + try: + json_dict = json.loads(jsontext) + return ierror.WXBizMsgCrypt_OK, json_dict['encrypt'] + except Exception as e: + print(e) + return ierror.WXBizMsgCrypt_ParseJson_Error, None + def generate(self, encrypt, signature, timestamp, nonce): + """生成json消息 + @param encrypt: 加密后的消息密文 + @param signature: 安全签名 + @param timestamp: 时间戳 + @param nonce: 随机字符串 + @return: 生成的json字符串 + """ + resp_dict = { + 'msg_encrypt' : encrypt, + 'msg_signaturet': signature, + 'timestamp' : timestamp, + 'nonce' : nonce, + } + resp_json = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict + return resp_json + + +class PKCS7Encoder(): + """提供基于PKCS7算法的加解密接口""" + + block_size = 32 + def encode(self, text): + """ 对需要加密的明文进行填充补位 + @param text: 需要进行填充补位操作的明文(bytes类型) + @return: 补齐明文字符串(bytes类型) + """ + 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 = bytes([amount_to_pad]) + # 确保text是bytes类型 + if isinstance(text, str): + text = text.encode('utf-8') + 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): + """提供接收和推送给企业微信消息的加解密接口""" + + def __init__(self,key): + + #self.key = base64.b64decode(key+"=") + self.key = key + # 设置加解密模式为AES的CBC模式 + self.mode = AES.MODE_CBC + + + def encrypt(self,text,receiveid): + """对明文进行加密 + @param text: 需要加密的明文 + @return: 加密得到的字符串 + """ + # 将text和receiveid转换为bytes类型 + text = text.encode('utf-8') + receiveid = receiveid.encode('utf-8') + + # 16位随机字符串添加到明文开头 + random_str = self.get_random_str().encode('utf-8') + text = random_str + struct.pack("I",socket.htonl(len(text))) + text + receiveid + + # 使用自定义的填充方式对明文进行补位填充 + pkcs7 = PKCS7Encoder() + text = pkcs7.encode(text) + + # 加密 + cryptor = AES.new(self.key,self.mode,self.key[:16]) + try: + ciphertext = cryptor.encrypt(text) + # 使用BASE64对加密后的字符串进行编码 + return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext) + except Exception as e: + print(e) + return ierror.WXBizMsgCrypt_EncryptAES_Error,None + + def decrypt(self,text,receiveid): + """对解密后的明文进行补位删除 + @param text: 密文 + @return: 删除填充补位后的明文 + """ + try: + cryptor = AES.new(self.key,self.mode,self.key[:16]) + # 使用BASE64对密文进行解码,然后AES-CBC解密 + plain_text = cryptor.decrypt(base64.b64decode(text)) + except Exception as e: + print(e) + return ierror.WXBizMsgCrypt_DecryptAES_Error,None + try: + pad = plain_text[-1] + # 去掉补位字符串 + #pkcs7 = PKCS7Encoder() + #plain_text = pkcs7.encode(plain_text) + # 去除16位随机字符串 + content = plain_text[16:-pad] + json_len = socket.ntohl(struct.unpack("I",content[ : 4])[0]) + json_content = content[4 : json_len+4].decode('utf-8') + from_receiveid = content[json_len+4:].decode('utf-8') + except Exception as e: + print(e) + return ierror.WXBizMsgCrypt_IllegalBuffer,None + if from_receiveid != receiveid: + print("receiveid not match", receiveid, from_receiveid) + return ierror.WXBizMsgCrypt_ValidateCorpid_Error,None + return 0,json_content + + def get_random_str(self): + """ 随机生成16位字符串 + @return: 16位字符串 + """ + rule = string.ascii_letters + string.digits + str = random.sample(rule, 16) + return "".join(str) + +class WXBizJsonMsgCrypt(object): + #构造函数 + def __init__(self,sToken,sEncodingAESKey,sReceiveId): + try: + self.key = base64.b64decode(sEncodingAESKey+"=") + assert len(self.key) == 32 + except: + throw_exception("[error]: EncodingAESKey unvalid !", FormatException) + # return ierror.WXBizMsgCrypt_IllegalAesKey,None + self.m_sToken = sToken + self.m_sReceiveId = sReceiveId + + #验证URL + #@param sMsgSignature: 签名串,对应URL参数的msg_signature + #@param sTimeStamp: 时间戳,对应URL参数的timestamp + #@param sNonce: 随机串,对应URL参数的nonce + #@param sEchoStr: 随机串,对应URL参数的echostr + #@param sReplyEchoStr: 解密之后的echostr,当return返回0时有效 + #@return:成功0,失败返回对应的错误码 + + def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr): + sha1 = SHA1() + ret,signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr) + if ret != 0: + return ret, None + if not signature == sMsgSignature: + return ierror.WXBizMsgCrypt_ValidateSignature_Error, None + pc = Prpcrypt(self.key) + ret,sReplyEchoStr = pc.decrypt(sEchoStr,self.m_sReceiveId) + return ret,sReplyEchoStr + + def EncryptMsg(self, sReplyMsg, sNonce, timestamp = None): + #将企业回复用户的消息加密打包 + #@param sReplyMsg: 企业号待回复用户的消息,json格式的字符串 + #@param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间 + #@param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce + #sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串, + #return:成功0,sEncryptMsg,失败返回对应的错误码None + pc = Prpcrypt(self.key) + ret,encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId) + if ret != 0: + return ret,None + if timestamp is None: + timestamp = str(int(time.time())) + # 生成安全签名 + sha1 = SHA1() + ret,signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt) + if ret != 0: + return ret,None + jsonParse = JsonParse() + return ret,jsonParse.generate(encrypt, signature, timestamp, sNonce) + + def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce): + # 检验消息的真实性,并且获取解密后的明文 + # @param sMsgSignature: 签名串,对应URL参数的msg_signature + # @param sTimeStamp: 时间戳,对应URL参数的timestamp + # @param sNonce: 随机串,对应URL参数的nonce + # @param sPostData: 密文,对应POST请求的数据 + # json_content: 解密后的原文,当return返回0时有效 + # @return: 成功0,失败返回对应的错误码 + # 验证安全签名 + jsonParse = JsonParse() + ret,encrypt = jsonParse.extract(sPostData) + if ret != 0: + return ret, None + sha1 = SHA1() + ret,signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt) + if ret != 0: + return ret, None + if not signature == sMsgSignature: + print("signature not match") + print(signature) + return ierror.WXBizMsgCrypt_ValidateSignature_Error, None + pc = Prpcrypt(self.key) + ret,json_content = pc.decrypt(encrypt,self.m_sReceiveId) + return ret,json_content + + diff --git a/callback_json_python3/ierror.py b/callback_json_python3/ierror.py new file mode 100644 index 0000000..51c02c3 --- /dev/null +++ b/callback_json_python3/ierror.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 01:53:58 PM CST +# File Name: ierror.py +# Description:定义错误码含义 +######################################################################### +WXBizMsgCrypt_OK = 0 +WXBizMsgCrypt_ValidateSignature_Error = -40001 +WXBizMsgCrypt_ParseJson_Error = -40002 +WXBizMsgCrypt_ComputeSignature_Error = -40003 +WXBizMsgCrypt_IllegalAesKey = -40004 +WXBizMsgCrypt_ValidateCorpid_Error = -40005 +WXBizMsgCrypt_EncryptAES_Error = -40006 +WXBizMsgCrypt_DecryptAES_Error = -40007 +WXBizMsgCrypt_IllegalBuffer = -40008 +WXBizMsgCrypt_EncodeBase64_Error = -40009 +WXBizMsgCrypt_DecodeBase64_Error = -40010 +WXBizMsgCrypt_GenReturnJson_Error = -40011 diff --git a/callback_python3/Readme.txt b/callback_python3/Readme.txt new file mode 100644 index 0000000..7b8dd0c --- /dev/null +++ b/callback_python3/Readme.txt @@ -0,0 +1,5 @@ +注意事项 +1.WXBizMsgCrypt.py文件封装了WXBizMsgCrypt接口类(Python3以及以上版本使用 WXBizMsgCrypt3.py),提供了用户接入企业微信的三个接口,Sample.py文件提供了如何使用这三个接口的示例,ierror.py提供了错误码。 +2.WXBizMsgCrypt封装了VerifyURL, DecryptMsg, EncryptMsg三个接口,分别用于开发者验证回调url,收到用户回复消息的解密以及开发者回复消息的加密过程。使用方法可以参考Sample.py文件。 +3.加解密协议请参考企业微信官方文档。 +4.本代码用到了pycrypto第三方库,请开发者自行安装此库再使用。 diff --git a/callback_python3/Sample.py b/callback_python3/Sample.py new file mode 100644 index 0000000..68b1f9d --- /dev/null +++ b/callback_python3/Sample.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 03:55:41 PM CST +# File Name: Sample.py +# Description: WXBizMsgCrypt 使用demo文件 +######################################################################### +from WXBizMsgCrypt import WXBizMsgCrypt +import xml.etree.cElementTree as ET +import sys + +if __name__ == "__main__": + #假设企业在企业微信后台上设置的参数如下 + sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" + sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + sCorpID = "ww1436e0e65a779aee" + ''' + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业号会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + ''' + wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID) + #sVerifyMsgSig=HttpUtils.ParseUrl("msg_signature") + #ret = wxcpt.VerifyAESKey() + #print ret + sVerifyMsgSig="012bc692d0a58dd4b10f8dfe5c4ac00ae211ebeb" + #sVerifyTimeStamp=HttpUtils.ParseUrl("timestamp") + sVerifyTimeStamp="1476416373" + #sVerifyNonce=HttpUitls.ParseUrl("nonce") + sVerifyNonce="47744683" + #sVerifyEchoStr=HttpUtils.ParseUrl("echostr") + sVerifyEchoStr="fsi1xnbH4yQh0+PJxcOdhhK6TDXkjMyhEPA7xB2TGz6b+g7xyAbEkRxN/3cNXW9qdqjnoVzEtpbhnFyq6SVHyA==" + ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr) + if(ret!=0): + print("ERR: VerifyURL ret: " + str(ret)) + sys.exit(1) + #验证URL成功,将sEchoStr返回给企业号 + #HttpUtils.SetResponse(sEchoStr) + + ''' + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + Content-Length: 613 + + + + + 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 3.将post请求的数据进行xml解析,并将标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + ''' + # sReqMsgSig = HttpUtils.ParseUrl("msg_signature") + sReqMsgSig = "0c3914025cb4b4d68103f6bfc8db550f79dcf48e" + sReqTimeStamp = "1476422779" + sReqNonce = "1597212914" + sReqData = "\n\n\n" + ret,sMsg=wxcpt.DecryptMsg( sReqData, sReqMsgSig, sReqTimeStamp, sReqNonce) + print(ret,sMsg) + if( ret!=0 ): + print("ERR: DecryptMsg ret: " + str(ret)) + sys.exit(1) + # 解密成功,sMsg即为xml格式的明文 + # TODO: 对明文的处理 + # For example: + xml_tree = ET.fromstring(sMsg) + content = xml_tree.find("Content").text + print(content) + # ... + # ... + + ''' + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的xml串。 + 假设企业需要回复用户的明文如下: + + + + 1348831860 + + + 1234567890123456 + 128 + + + 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成xml格式的字符串,发送给企业号。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + ''' + sRespData = "ww1436e0e65a779aeeChenJiaShun1476422779text你好14564537201000002" + ret,sEncryptMsg=wxcpt.EncryptMsg(sRespData, sReqNonce, sReqTimeStamp) + if( ret!=0 ): + print("ERR: EncryptMsg ret: " + str(ret)) + sys.exit(1) + print("sEncryptMsg: " + sEncryptMsg) + #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 + #TODO: + #HttpUitls.SetResponse(sEncryptMsg) diff --git a/callback/WXBizMsgCrypt3.py b/callback_python3/WXBizMsgCrypt.py similarity index 100% rename from callback/WXBizMsgCrypt3.py rename to callback_python3/WXBizMsgCrypt.py diff --git a/callback_python3/ierror.py b/callback_python3/ierror.py new file mode 100644 index 0000000..6678fec --- /dev/null +++ b/callback_python3/ierror.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 01:53:58 PM CST +# File Name: ierror.py +# Description:定义错误码含义 +######################################################################### +WXBizMsgCrypt_OK = 0 +WXBizMsgCrypt_ValidateSignature_Error = -40001 +WXBizMsgCrypt_ParseXml_Error = -40002 +WXBizMsgCrypt_ComputeSignature_Error = -40003 +WXBizMsgCrypt_IllegalAesKey = -40004 +WXBizMsgCrypt_ValidateCorpid_Error = -40005 +WXBizMsgCrypt_EncryptAES_Error = -40006 +WXBizMsgCrypt_DecryptAES_Error = -40007 +WXBizMsgCrypt_IllegalBuffer = -40008 +WXBizMsgCrypt_EncodeBase64_Error = -40009 +WXBizMsgCrypt_DecodeBase64_Error = -40010 +WXBizMsgCrypt_GenReturnXml_Error = -40011 From 00871f85023e10a8002f79ebb742272eb1e0ffa7 Mon Sep 17 00:00:00 2001 From: sbzhu Date: Thu, 26 Jun 2025 13:17:18 +0800 Subject: [PATCH 07/14] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 140e9fe..069005c 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ golang : https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail. │   ├── examples // API接口的测试用例 │   ├── README.md │   └── src // API接口的关键逻辑 -├── callback // 加解密库(python2, xml格式) -├── callback_json // 加解密库(Python2, json格式, 仅适用于企业机器人/智能机器人) -├── callback_json_python3 // 加解密库(Python3, json格式, 仅适用于企业机器人/智能机器人) -├── callback_python3 // 加解密库(python2, xml格式) +├── callback // 加解密库(python2, xml格式) +├── callback_json // 加解密库(Python2, json格式, 仅适用于企业机器人/智能机器人) +├── callback_json_python3 // 加解密库(Python3, json格式, 仅适用于企业机器人/智能机器人) +├── callback_python3 // 加解密库(python2, xml格式) ├── conf.py └── README.md From eee725a65456cf4cf755ebe35c777434b59ec94f Mon Sep 17 00:00:00 2001 From: sbzhu Date: Thu, 26 Jun 2025 13:17:53 +0800 Subject: [PATCH 08/14] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 069005c..46eb88b 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ golang : https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail. │   ├── examples // API接口的测试用例 │   ├── README.md │   └── src // API接口的关键逻辑 -├── callback // 加解密库(python2, xml格式) -├── callback_json // 加解密库(Python2, json格式, 仅适用于企业机器人/智能机器人) -├── callback_json_python3 // 加解密库(Python3, json格式, 仅适用于企业机器人/智能机器人) -├── callback_python3 // 加解密库(python2, xml格式) +├── callback // 加解密库,python2, xml格式) +├── callback_json // 加解密库,Python2, json格式, 仅适用于企业机器人/智能机器人 +├── callback_json_python3 // 加解密库,Python3, json格式, 仅适用于企业机器人/智能机器人 +├── callback_python3 // 加解密库,python2, xml格式 ├── conf.py └── README.md From 0af1ebe214d2f538e88cbebedc75159721d9b55c Mon Sep 17 00:00:00 2001 From: sbzhu Date: Thu, 26 Jun 2025 13:18:21 +0800 Subject: [PATCH 09/14] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 46eb88b..6d3cca3 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,13 @@ golang : https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail. │   ├── README.md │   └── src // API接口的关键逻辑 ├── callback // 加解密库,python2, xml格式) + ├── callback_json // 加解密库,Python2, json格式, 仅适用于企业机器人/智能机器人 + ├── callback_json_python3 // 加解密库,Python3, json格式, 仅适用于企业机器人/智能机器人 + ├── callback_python3 // 加解密库,python2, xml格式 + ├── conf.py └── README.md From f20767a3e091e43c20fbd351a6789e7fe846593d Mon Sep 17 00:00:00 2001 From: sbzhu Date: Wed, 16 Jul 2025 14:12:44 +0800 Subject: [PATCH 10/14] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 测试解密 --- callback_json_python3/Sample.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/callback_json_python3/Sample.py b/callback_json_python3/Sample.py index 740de23..31e2618 100644 --- a/callback_json_python3/Sample.py +++ b/callback_json_python3/Sample.py @@ -8,6 +8,7 @@ ######################################################################### from WXBizJsonMsgCrypt import WXBizJsonMsgCrypt import sys +import json if __name__ == "__main__": #假设企业在企业微信后台上设置的参数如下 @@ -106,3 +107,17 @@ print(sEncryptMsg) #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 print("==============================") + + ''' + 对上面加密的包进行解密 + ''' + sReqMsgSig = json.loads(sEncryptMsg)['msgsignature'] + sReqTimeStamp = json.loads(sEncryptMsg)['timestamp'] + sReqNonce = json.loads(sEncryptMsg)['nonce'] + + ret,sMsg=wxcpt.DecryptMsg( sEncryptMsg, sReqMsgSig, sReqTimeStamp, sReqNonce) + if( ret!=0 ): + print("ERR: DecryptMsg ret: " + str(ret)) + sys.exit(1) + else: + print(sMsg) From f6adf5510808755e5300af0881e9b97abe3e21ca Mon Sep 17 00:00:00 2001 From: sbzhu Date: Wed, 16 Jul 2025 14:13:31 +0800 Subject: [PATCH 11/14] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- callback_python3/Sample.py | 232 ++++++++++++++++++++----------------- 1 file changed, 123 insertions(+), 109 deletions(-) diff --git a/callback_python3/Sample.py b/callback_python3/Sample.py index 68b1f9d..31e2618 100644 --- a/callback_python3/Sample.py +++ b/callback_python3/Sample.py @@ -1,109 +1,123 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################### -# Author: jonyqin -# Created Time: Thu 11 Sep 2014 03:55:41 PM CST -# File Name: Sample.py -# Description: WXBizMsgCrypt 使用demo文件 -######################################################################### -from WXBizMsgCrypt import WXBizMsgCrypt -import xml.etree.cElementTree as ET -import sys - -if __name__ == "__main__": - #假设企业在企业微信后台上设置的参数如下 - sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" - sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" - sCorpID = "ww1436e0e65a779aee" - ''' - ------------使用示例一:验证回调URL--------------- - *企业开启回调模式时,企业号会向验证url发送一个get请求 - 假设点击验证时,企业收到类似请求: - * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D - * HTTP/1.1 Host: qy.weixin.qq.com - - 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), - 这一步注意作URL解码。 - 2.验证消息体签名的正确性 - 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 - 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 - ''' - wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID) - #sVerifyMsgSig=HttpUtils.ParseUrl("msg_signature") - #ret = wxcpt.VerifyAESKey() - #print ret - sVerifyMsgSig="012bc692d0a58dd4b10f8dfe5c4ac00ae211ebeb" - #sVerifyTimeStamp=HttpUtils.ParseUrl("timestamp") - sVerifyTimeStamp="1476416373" - #sVerifyNonce=HttpUitls.ParseUrl("nonce") - sVerifyNonce="47744683" - #sVerifyEchoStr=HttpUtils.ParseUrl("echostr") - sVerifyEchoStr="fsi1xnbH4yQh0+PJxcOdhhK6TDXkjMyhEPA7xB2TGz6b+g7xyAbEkRxN/3cNXW9qdqjnoVzEtpbhnFyq6SVHyA==" - ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr) - if(ret!=0): - print("ERR: VerifyURL ret: " + str(ret)) - sys.exit(1) - #验证URL成功,将sEchoStr返回给企业号 - #HttpUtils.SetResponse(sEchoStr) - - ''' - ------------使用示例二:对用户回复的消息解密--------------- - 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 - 假设企业收到企业微信的回调消息如下: - POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 - Host: qy.weixin.qq.com - Content-Length: 613 - - - - - 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) - 2.验证消息体签名的正确性。 3.将post请求的数据进行xml解析,并将标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 - 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 - ''' - # sReqMsgSig = HttpUtils.ParseUrl("msg_signature") - sReqMsgSig = "0c3914025cb4b4d68103f6bfc8db550f79dcf48e" - sReqTimeStamp = "1476422779" - sReqNonce = "1597212914" - sReqData = "\n\n\n" - ret,sMsg=wxcpt.DecryptMsg( sReqData, sReqMsgSig, sReqTimeStamp, sReqNonce) - print(ret,sMsg) - if( ret!=0 ): - print("ERR: DecryptMsg ret: " + str(ret)) - sys.exit(1) - # 解密成功,sMsg即为xml格式的明文 - # TODO: 对明文的处理 - # For example: - xml_tree = ET.fromstring(sMsg) - content = xml_tree.find("Content").text - print(content) - # ... - # ... - - ''' - ------------使用示例三:企业回复用户消息的加密--------------- - 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的xml串。 - 假设企业需要回复用户的明文如下: - - - - 1348831860 - - - 1234567890123456 - 128 - - - 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 - 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成xml格式的字符串,发送给企业号。 - 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 - ''' - sRespData = "ww1436e0e65a779aeeChenJiaShun1476422779text你好14564537201000002" - ret,sEncryptMsg=wxcpt.EncryptMsg(sRespData, sReqNonce, sReqTimeStamp) - if( ret!=0 ): - print("ERR: EncryptMsg ret: " + str(ret)) - sys.exit(1) - print("sEncryptMsg: " + sEncryptMsg) - #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 - #TODO: - #HttpUitls.SetResponse(sEncryptMsg) +#!/usr/bin/env python +# -*- coding: utf-8 -*- +######################################################################### +# Author: jonyqin +# Created Time: Thu 11 Sep 2014 03:55:41 PM CST +# File Name: Sample.py +# Description: WXBizJsonMsgCrypt 使用demo文件 +######################################################################### +from WXBizJsonMsgCrypt import WXBizJsonMsgCrypt +import sys +import json + +if __name__ == "__main__": + #假设企业在企业微信后台上设置的参数如下 + sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" + sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + sCorpID = "ww1436e0e65a779aee" + ''' + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业号会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + ''' + wxcpt=WXBizJsonMsgCrypt(sToken,sEncodingAESKey,sCorpID) + sVerifyMsgSig="012bc692d0a58dd4b10f8dfe5c4ac00ae211ebeb" + sVerifyTimeStamp="1476416373" + sVerifyNonce="47744683" + sVerifyEchoStr="fsi1xnbH4yQh0+PJxcOdhhK6TDXkjMyhEPA7xB2TGz6b+g7xyAbEkRxN/3cNXW9qdqjnoVzEtpbhnFyq6SVHyA==" + ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr) + if(ret!=0): + print("ERR: VerifyURL ret: " + str(ret)) + sys.exit(1) + else: + print("done VerifyURL") + #验证URL成功,将sEchoStr返回给企业号 + + print("==============================") + ''' + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=e3647471e395139e2308c1fa963f2d648a00b90e×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + + { + "tousername": "wx5823bf96d3bd56c7", + "encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", + "agentid": 218 + } + + 企业收到post请求之后应该 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 3.将post请求的数据进行json解析,并将"encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + ''' + + sReqNonce = "1372623149" + sReqTimeStamp = "1409659813" + + sReqMsgSig = "e3647471e395139e2308c1fa963f2d648a00b90e" + sReqData = '{ "tousername": "wx5823bf96d3bd56c7", "encrypt": "cjhLUX7UU4yCSelv1vz7T0zT8huF51bAMVWriNvO1FMegHrQZNrtvRxbwf0fUPsFvwqR0U0fgiJNEA5Y30F2MoI2S7vv3EjVQ68C0cjw9frBoUE2Hj0BvFp9h3u6Vbsg4lc1C8AtHdaN8orKuNKkLRLuYEL52R1J3v8olJGZRLnRdVKIivixmX/eQpzgeExtp20jI1HxRP1AAZ6xZoILdqDPO549LO4WeG+685JRUTdiwcY5fjZlqeMxuT4PpMn1X9OWsS7NRj06Wa5E3Tvg4twjWp39KPfOdRte6P1T4JU=", "agentid": 218 }'; + ret,sMsg=wxcpt.DecryptMsg( sReqData, sReqMsgSig, sReqTimeStamp, sReqNonce) + if( ret!=0 ): + print("ERR: DecryptMsg ret: " + str(ret)) + sys.exit(1) + else: + print(sMsg) + # 解密成功,sMsg即为json格式的明文 + # TODO: 对明文的处理 + # ... + # ... + + print("==============================") + + ''' + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。 + 假设企业需要回复用户的明文如下: + + { + "ToUserName": "mycreate", + "FromUserName":"wx5823bf96d3bd56c7", + "CreateTime": 1348831860, + "MsgType": "text", + "Content": "this is a test", + "MsgId": 1234567890123456, + "AgentID": 128 + } + + 为了将此段明文回复给用户,企业应: 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业号。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + ''' + #sRespData = ' { "ToUserName": "mycreate", "FromUserName":"wx5823bf96d3bd56c7", "CreateTime": 1348831860, "MsgType": "text", "Content": "this is a test", "MsgId": 1234567890123456, "AgentID": 128 }'; + sRespData = '{ "ToUserName": "wx5823bf96d3bd56c7", "FromUserName": :mycreate", "CreateTime": 1409659813, "MsgType": "text", "Content": "hello", "MsgId": 4561255354251345929, "AgentID": 218}' + ret,sEncryptMsg=wxcpt.EncryptMsg(sRespData, sReqNonce, sReqTimeStamp) + if( ret!=0 ): + print("ERR: EncryptMsg ret: " + str(ret)) + sys.exit(1) + else: + print(sEncryptMsg) + #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 + print("==============================") + + ''' + 对上面加密的包进行解密 + ''' + sReqMsgSig = json.loads(sEncryptMsg)['msgsignature'] + sReqTimeStamp = json.loads(sEncryptMsg)['timestamp'] + sReqNonce = json.loads(sEncryptMsg)['nonce'] + + ret,sMsg=wxcpt.DecryptMsg( sEncryptMsg, sReqMsgSig, sReqTimeStamp, sReqNonce) + if( ret!=0 ): + print("ERR: DecryptMsg ret: " + str(ret)) + sys.exit(1) + else: + print(sMsg) From 609f46003566518d4bc24bc54d736deeddda0e08 Mon Sep 17 00:00:00 2001 From: sbzhu Date: Wed, 16 Jul 2025 14:14:11 +0800 Subject: [PATCH 12/14] Update Sample.py --- callback/Sample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/callback/Sample.py b/callback/Sample.py index 4915ff2..842999f 100644 --- a/callback/Sample.py +++ b/callback/Sample.py @@ -105,4 +105,4 @@ sys.exit(1) #ret == 0 加密成功,企业需要将sEncryptMsg返回给企业号 #TODO: - #HttpUitls.SetResponse(sEncryptMsg) + #HttpUitls.SetResponse(sEncryptMsg) #测试解密 From 16e0b0524e64ef75bb5040f8efd5172f64e18e21 Mon Sep 17 00:00:00 2001 From: sbzhu Date: Wed, 16 Jul 2025 15:50:58 +0800 Subject: [PATCH 13/14] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- callback_json_python3/WXBizJsonMsgCrypt.py | 33 +++++++++------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/callback_json_python3/WXBizJsonMsgCrypt.py b/callback_json_python3/WXBizJsonMsgCrypt.py index b3039fb..a954283 100644 --- a/callback_json_python3/WXBizJsonMsgCrypt.py +++ b/callback_json_python3/WXBizJsonMsgCrypt.py @@ -141,33 +141,29 @@ def __init__(self,key): # 设置加解密模式为AES的CBC模式 self.mode = AES.MODE_CBC - - def encrypt(self,text,receiveid): + + def encrypt(self, text, receiveid): """对明文进行加密 @param text: 需要加密的明文 @return: 加密得到的字符串 - """ - # 将text和receiveid转换为bytes类型 - text = text.encode('utf-8') - receiveid = receiveid.encode('utf-8') - + """ # 16位随机字符串添加到明文开头 - random_str = self.get_random_str().encode('utf-8') - text = random_str + struct.pack("I",socket.htonl(len(text))) + text + receiveid - + text = text.encode() + text = self.get_random_str() + struct.pack("I", socket.htonl(len(text))) + text + receiveid.encode() + # 使用自定义的填充方式对明文进行补位填充 pkcs7 = PKCS7Encoder() text = pkcs7.encode(text) - - # 加密 - cryptor = AES.new(self.key,self.mode,self.key[:16]) + # 加密 + cryptor = AES.new(self.key, self.mode, self.key[:16]) try: ciphertext = cryptor.encrypt(text) # 使用BASE64对加密后的字符串进行编码 return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext) except Exception as e: - print(e) - return ierror.WXBizMsgCrypt_EncryptAES_Error,None + logger = logging.getLogger() + logger.error(e) + return ierror.WXBizMsgCrypt_EncryptAES_Error, None def decrypt(self,text,receiveid): """对解密后的明文进行补位删除 @@ -202,10 +198,8 @@ def decrypt(self,text,receiveid): def get_random_str(self): """ 随机生成16位字符串 @return: 16位字符串 - """ - rule = string.ascii_letters + string.digits - str = random.sample(rule, 16) - return "".join(str) + """ + return str(random.randint(1000000000000000, 9999999999999999)).encode() class WXBizJsonMsgCrypt(object): #构造函数 @@ -247,6 +241,7 @@ def EncryptMsg(self, sReplyMsg, sNonce, timestamp = None): #return:成功0,sEncryptMsg,失败返回对应的错误码None pc = Prpcrypt(self.key) ret,encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId) + encrypt = encrypt.decode('utf-8') if ret != 0: return ret,None if timestamp is None: From 71441bf837646984a64065d8faf539fe13eb72c4 Mon Sep 17 00:00:00 2001 From: abelzhu Date: Fri, 8 Aug 2025 20:20:27 +0800 Subject: [PATCH 14/14] =?UTF-8?q?=E5=88=A0=E6=8E=89token=E5=92=8Caeskey?= =?UTF-8?q?=EF=BC=8C=E8=AE=A9=E5=BC=80=E5=8F=91=E8=80=85=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E5=A1=AB=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- callback/Sample.py | 6 +++--- callback_json/Sample.py | 6 +++--- callback_json_python3/Sample.py | 6 +++--- callback_python3/Sample.py | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/callback/Sample.py b/callback/Sample.py index 842999f..f1faa7c 100644 --- a/callback/Sample.py +++ b/callback/Sample.py @@ -11,9 +11,9 @@ import sys if __name__ == "__main__": - #假设企业在企业微信后台上设置的参数如下 - sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" - sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + # 企业在企业微信后台上设置的密钥相关配置在这里 TODO + sToken = "xxxxxxx" + sEncodingAESKey = "xxxxxxx" sCorpID = "ww1436e0e65a779aee" ''' ------------使用示例一:验证回调URL--------------- diff --git a/callback_json/Sample.py b/callback_json/Sample.py index ab47c01..5819337 100644 --- a/callback_json/Sample.py +++ b/callback_json/Sample.py @@ -10,9 +10,9 @@ import sys if __name__ == "__main__": - #假设企业在企业微信后台上设置的参数如下 - sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" - sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + # 企业在企业微信后台上设置的密钥相关配置在这里 TODO + sToken = "xxxxxxx" + sEncodingAESKey = "xxxxxxx" sCorpID = "ww1436e0e65a779aee" ''' ------------使用示例一:验证回调URL--------------- diff --git a/callback_json_python3/Sample.py b/callback_json_python3/Sample.py index 31e2618..7b76caf 100644 --- a/callback_json_python3/Sample.py +++ b/callback_json_python3/Sample.py @@ -11,9 +11,9 @@ import json if __name__ == "__main__": - #假设企业在企业微信后台上设置的参数如下 - sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" - sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + # 企业在企业微信后台上设置的密钥相关配置在这里 TODO + sToken = "xxxxxxx" + sEncodingAESKey = "xxxxxxx" sCorpID = "ww1436e0e65a779aee" ''' ------------使用示例一:验证回调URL--------------- diff --git a/callback_python3/Sample.py b/callback_python3/Sample.py index 31e2618..7b76caf 100644 --- a/callback_python3/Sample.py +++ b/callback_python3/Sample.py @@ -11,9 +11,9 @@ import json if __name__ == "__main__": - #假设企业在企业微信后台上设置的参数如下 - sToken = "hJqcu3uJ9Tn2gXPmxx2w9kkCkCE2EPYo" - sEncodingAESKey = "6qkdMrq68nTKduznJYO1A37W2oEgpkMUvkttRToqhUt" + # 企业在企业微信后台上设置的密钥相关配置在这里 TODO + sToken = "xxxxxxx" + sEncodingAESKey = "xxxxxxx" sCorpID = "ww1436e0e65a779aee" ''' ------------使用示例一:验证回调URL---------------