crypt.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #encoding=utf-8
  2. import base64
  3. import string
  4. import random
  5. import hashlib
  6. import time
  7. import struct
  8. from Crypto.Cipher import AES
  9. import xml.etree.cElementTree as ET
  10. import sys
  11. import socket
  12. reload(sys)
  13. sys.setdefaultencoding('utf-8')
  14. WXBizMsgCrypt_OK = 0
  15. WXBizMsgCrypt_ValidateSignature_Error = -40001
  16. WXBizMsgCrypt_ParseXml_Error = -40002
  17. WXBizMsgCrypt_ComputeSignature_Error = -40003
  18. WXBizMsgCrypt_IllegalAesKey = -40004
  19. WXBizMsgCrypt_ValidateAppidOrCorpid_Error = -40005
  20. WXBizMsgCrypt_EncryptAES_Error = -40006
  21. WXBizMsgCrypt_DecryptAES_Error = -40007
  22. WXBizMsgCrypt_IllegalBuffer = -40008
  23. WXBizMsgCrypt_EncodeBase64_Error = -40009
  24. WXBizMsgCrypt_DecodeBase64_Error = -40010
  25. WXBizMsgCrypt_GenReturnXml_Error = -40011
  26. """
  27. 关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案
  28. 请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。
  29. 下载后,按照README中的“Installation”小节的提示进行pycrypto安装。
  30. """
  31. class FormatException(Exception):
  32. pass
  33. def throw_exception(message, exception_class=FormatException):
  34. """my define raise exception function"""
  35. raise exception_class(message)
  36. class SHA1:
  37. """计算公众平台的消息签名接口"""
  38. def getSHA1(self, token, timestamp, nonce, encrypt):
  39. """用SHA1算法生成安全签名
  40. @param token: 票据
  41. @param timestamp: 时间戳
  42. @param encrypt: 密文
  43. @param nonce: 随机字符串
  44. @return: 安全签名
  45. """
  46. try:
  47. sortlist = [token, timestamp, nonce, encrypt]
  48. sortlist.sort()
  49. sha = hashlib.sha1()
  50. sha.update("".join(sortlist))
  51. return WXBizMsgCrypt_OK, sha.hexdigest()
  52. except Exception:
  53. return WXBizMsgCrypt_ComputeSignature_Error, None
  54. class XMLParse:
  55. """提供提取消息格式中的密文及生成回复消息格式的接口"""
  56. AES_TEXT_RESPONSE_TEMPLATE = """<xml>
  57. <Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
  58. <MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
  59. <TimeStamp>%(timestamp)s</TimeStamp>
  60. <Nonce><![CDATA[%(nonce)s]]></Nonce>
  61. </xml>"""
  62. def extract(self, xmltext):
  63. """提取出xml数据包中的加密消息
  64. @param xmltext: 待提取的xml字符串
  65. @return: 提取出的加密消息字符串
  66. """
  67. try:
  68. xml_tree = ET.fromstring(xmltext)
  69. encrypt = xml_tree.find("Encrypt")
  70. touser_name = xml_tree.find("ToUserName")
  71. if touser_name != None:
  72. touser_name = touser_name.text
  73. return WXBizMsgCrypt_OK, encrypt.text, touser_name
  74. except Exception:
  75. return WXBizMsgCrypt_ParseXml_Error, None, None
  76. def generate(self, encrypt, signature, timestamp, nonce):
  77. """生成xml消息
  78. @param encrypt: 加密后的消息密文
  79. @param signature: 安全签名
  80. @param timestamp: 时间戳
  81. @param nonce: 随机字符串
  82. @return: 生成的xml字符串
  83. """
  84. resp_dict = {
  85. 'msg_encrypt': encrypt,
  86. 'msg_signaturet': signature,
  87. 'timestamp': timestamp,
  88. 'nonce': nonce,
  89. }
  90. resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
  91. return resp_xml
  92. class PKCS7Encoder():
  93. """提供基于PKCS7算法的加解密接口"""
  94. block_size = 32
  95. def encode(self, text):
  96. """ 对需要加密的明文进行填充补位
  97. @param text: 需要进行填充补位操作的明文
  98. @return: 补齐明文字符串
  99. """
  100. text_length = len(text)
  101. # 计算需要填充的位数
  102. amount_to_pad = self.block_size - (text_length % self.block_size)
  103. if amount_to_pad == 0:
  104. amount_to_pad = self.block_size
  105. # 获得补位所用的字符
  106. pad = chr(amount_to_pad)
  107. return text + pad * amount_to_pad
  108. def decode(self, decrypted):
  109. """删除解密后明文的补位字符
  110. @param decrypted: 解密后的明文
  111. @return: 删除补位字符后的明文
  112. """
  113. pad = ord(decrypted[-1])
  114. if pad < 1 or pad > 32:
  115. pad = 0
  116. return decrypted[:-pad]
  117. class Prpcrypt(object):
  118. """提供接收和推送给公众平台消息的加解密接口"""
  119. def __init__(self, key):
  120. #self.key = base64.b64decode(key+"=")
  121. self.key = key
  122. # 设置加解密模式为AES的CBC模式
  123. self.mode = AES.MODE_CBC
  124. def encrypt(self, text, appid):
  125. """对明文进行加密
  126. @param text: 需要加密的明文
  127. @return: 加密得到的字符串
  128. """
  129. # 16位随机字符串添加到明文开头
  130. text = self.get_random_str() + struct.pack(
  131. "I", socket.htonl(len(text))) + text + appid
  132. # 使用自定义的填充方式对明文进行补位填充
  133. pkcs7 = PKCS7Encoder()
  134. text = pkcs7.encode(text)
  135. # 加密
  136. cryptor = AES.new(self.key, self.mode, self.key[:16])
  137. try:
  138. ciphertext = cryptor.encrypt(text)
  139. # 使用BASE64对加密后的字符串进行编码
  140. return WXBizMsgCrypt_OK, base64.b64encode(ciphertext)
  141. except Exception:
  142. return WXBizMsgCrypt_EncryptAES_Error, None
  143. def decrypt(self, text, appid):
  144. """对解密后的明文进行补位删除
  145. @param text: 密文
  146. @return: 删除填充补位后的明文
  147. """
  148. try:
  149. cryptor = AES.new(self.key, self.mode, self.key[:16])
  150. # 使用BASE64对密文进行解码,然后AES-CBC解密
  151. plain_text = cryptor.decrypt(base64.b64decode(text))
  152. except Exception:
  153. return WXBizMsgCrypt_DecryptAES_Error, None
  154. try:
  155. pad = ord(plain_text[-1])
  156. # 去掉补位字符串
  157. #pkcs7 = PKCS7Encoder()
  158. #plain_text = pkcs7.encode(plain_text)
  159. # 去除16位随机字符串
  160. content = plain_text[16:-pad]
  161. xml_len = socket.ntohl(struct.unpack("I", content[:4])[0])
  162. xml_content = content[4:xml_len+4]
  163. from_appid = content[xml_len+4:]
  164. except Exception:
  165. return WXBizMsgCrypt_IllegalBuffer, None
  166. if from_appid != appid:
  167. return WXBizMsgCrypt_ValidateAppidOrCorpid_Error, None
  168. return 0, xml_content
  169. def get_random_str(self):
  170. """ 随机生成16位字符串
  171. @return: 16位字符串
  172. """
  173. rule = string.letters + string.digits
  174. str = random.sample(rule, 16)
  175. return "".join(str)
  176. class WXBizMsgCrypt(object):
  177. def __init__(self, sToken, sEncodingAESKey, sCorpId):
  178. try:
  179. self.key = base64.b64decode(sEncodingAESKey+"=")
  180. assert len(self.key) == 32
  181. except:
  182. throw_exception("[error]: EncodingAESKey unvalid !",
  183. FormatException)
  184. #return WXBizMsgCrypt_IllegalAesKey)
  185. self.m_sToken = sToken
  186. self.m_sCorpid = sCorpId
  187. def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
  188. sha1 = SHA1()
  189. ret, signature = sha1.getSHA1(self.m_sToken,
  190. sTimeStamp, sNonce, sEchoStr)
  191. if ret != 0:
  192. return ret, None
  193. if not signature == sMsgSignature:
  194. return WXBizMsgCrypt_ValidateSignature_Error, None
  195. pc = Prpcrypt(self.key)
  196. ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sCorpid)
  197. return ret, sReplyEchoStr
  198. def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
  199. pc = Prpcrypt(self.key)
  200. ret, encrypt = pc.encrypt(sReplyMsg, self.m_sCorpid)
  201. if ret != 0:
  202. return ret, None
  203. if timestamp is None:
  204. timestamp = str(int(time.time()))
  205. # 生成安全签名
  206. sha1 = SHA1()
  207. ret, signature = sha1.getSHA1(self.m_sToken, timestamp,
  208. sNonce, encrypt)
  209. if ret != 0:
  210. return ret, None
  211. xmlParse = XMLParse()
  212. return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
  213. def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
  214. xmlParse = XMLParse()
  215. ret, encrypt, touser_name = xmlParse.extract(sPostData)
  216. if ret != 0:
  217. return ret, None
  218. sha1 = SHA1()
  219. ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp,
  220. sNonce, encrypt)
  221. if ret != 0:
  222. return ret, None
  223. if not signature == sMsgSignature:
  224. return WXBizMsgCrypt_ValidateSignature_Error, None
  225. pc = Prpcrypt(self.key)
  226. ret, xml_content = pc.decrypt(encrypt, self.m_sCorpid)
  227. return ret, xml_content