text: # coding=utf-8
  from __future__ import absolute_import
  
  from Crypto.Cipher import AES
  import xml.etree.cElementTree as ET
  import base64
  import string
  import random
  import hashlib
  import time
  import struct
  import socket
  
  from . import ierror
  
  
  """
  About Crypto.Cipher module, ImportError: No module named 'Crypto' solutions
  Go to the official website https://www.dlitz.net/software/pycrypto/
  download pycrypto.
  After downloading, follow the instructions in the README "Installation"
  section of conduct pycrypto installation.
  """
  
  
  class FormatException(Exception):
      pass
  
  
  class SHA1:
      """signature"""
  
      def getSHA1(self, token, timestamp, nonce, encrypt):
          """
          @param token:  token
          @param timestamp: timestamp
          @param encrypt: encrypt text
          @param nonce: random string
          @return: signature
          """
          try:
              sortlist = [token, timestamp, nonce, encrypt]
              sortlist.sort()
              sha = hashlib.sha1()
              sha.update(''.join(sortlist))
              return ierror.WXBizMsgCrypt_OK, sha.hexdigest()
          except Exception:
              return ierror.WXBizMsgCrypt_ComputeSignature_Error, None
  
  
  class XMLParse:
      """get encrypt and generate a reply message"""
  
      # xml template
      AES_TEXT_RESPONSE_TEMPLATE = '''<xml>
      <Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
      <MsgSignature><![CDATA[%(msg_signature)s]]></MsgSignature>
      <TimeStamp>%(timestamp)s</TimeStamp>
      <Nonce><![CDATA[%(nonce)s]]></Nonce>
      </xml>'''
  
      def extract(self, xmltext):
          """ unpack encrypted message
          @param xmltext: xml text
          @return: encrypted message
          """
          try:
              xml_tree = ET.fromstring(xmltext)
              encrypt = xml_tree.find('Encrypt')
              touser_name = xml_tree.find('ToUserName')
              return ierror.WXBizMsgCrypt_OK, encrypt.text, touser_name.text
          except Exception:
              return ierror.WXBizMsgCrypt_ParseXml_Error, None, None
  
      def generate(self, encrypt, signature, timestamp, nonce):
          """Generate xml message
          @param encrypt: encrypted message text
          @param signature: signature
          @param timestamp: timestamp
          @param nonce: random string
          @return: xml text
          """
          resp_dict = {
              'msg_encrypt': encrypt,
              'msg_signature': signature,
              'timestamp': timestamp,
              'nonce': nonce,
          }
          resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
          return resp_xml
  
  
  class PKCS7Encoder():
      """PKCS7-based encryption and decryption algorithm"""
  
      block_size = 32
  
      def encode(self, text):
          """
          @param text: orignial plaintext
          @return: remedium plaintext
          """
          text_length = len(text)
          # count remedium
          amount_to_pad = self.block_size - (text_length % self.block_size)
          if amount_to_pad == 0:
              amount_to_pad = self.block_size
          # get remedium charector
          pad = chr(amount_to_pad)
          return text + pad * amount_to_pad
  
      def decode(self, decrypted):
          """Plaintext after you remove the decrypted
          @param decrypted: decrypted remedium plaintext
          @return: plaintext before remedium
          """
          pad = ord(decrypted[-1])
          if pad < 1 or pad > 32:
              pad = 0
          return decrypted[:-pad]
  
  
  class Prpcrypt(object):
      """Receiving and Pushed to wechat encryption and decryption of messages"""
  
      def __init__(self, key):
          # self.key = base64.b64decode(key+"=")
          self.key = key
          # Set encryption mode AES.MODE_CBC
          self.mode = AES.MODE_CBC
  
      def encrypt(self, text, appid):
          """
          @param text: text need to be encrypt
          @return: encrypted text
          """
          # 16 random string added to the beginning of the plaintext
          struct_pack_text = struct.pack('I', socket.htonl(len(text)))
          text = self.get_random_str() + struct_pack_text + text + appid
          # Use custom stuffing plaintext
          pkcs7 = PKCS7Encoder()
          text = pkcs7.encode(text)
          # Encryption
          cryptor = AES.new(self.key, self.mode, self.key[:16])
          try:
              ciphertext = cryptor.encrypt(text)
              # using BASE64 string is encoded
              return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext)
          except Exception:
              return ierror.WXBizMsgCrypt_EncryptAES_Error, None
  
      def decrypt(self, text, appid):
          """delete remedium for decrypted plaintext
          @param text: Ciphertext
          @return: plaintext with out remedium
          """
          try:
              cryptor = AES.new(self.key, self.mode, self.key[:16])
              # using BASE64 decode and decrypt AES-CBC
              plain_text = cryptor.decrypt(base64.b64decode(text))
          except Exception:
              return ierror.WXBizMsgCrypt_DecryptAES_Error, None
          try:
              pad = ord(plain_text[-1])
              # Remove the remedium
              # pkcs7 = PKCS7Encoder()
              # plain_text = pkcs7.encode(plain_text)
              # remove 16 random char
              content = plain_text[16:-pad]
              xml_len = socket.ntohl(struct.unpack('I', content[:4])[0])
              xml_content = content[4:xml_len + 4]
              from_appid = content[xml_len + 4:]
          except Exception:
              return ierror.WXBizMsgCrypt_IllegalBuffer, None
          if from_appid != appid:
              return ierror.WXBizMsgCrypt_ValidateAppid_Error, None
          return 0, xml_content
  
      def get_random_str(self):
          """
          @return: random 16 charector string
          """
          rule = string.letters + string.digits
          str_list = random.sample(rule, 16)
          return ''.join(str_list)
  
  
  class WXBizMsgCrypt(object):
      # Constructor
      # @param sToken: developer token
      # @param sEncodingAESKey: developer EncodingAESKey
      # @param sAppId: developer AppId
      def __init__(self, sToken, sEncodingAESKey, sAppId):
          try:
              self.key = base64.b64decode(sEncodingAESKey + '=')
              assert len(self.key) == 32
          except Exception:
              raise FormatException('[error]: EncodingAESKey invalid !')
              # return ierror.WXBizMsgCrypt_IllegalAesKey
          self.token = sToken
          self.appid = sAppId
  
      def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
          # package encrypted reply message
          # @param sReplyMsg: reply message in xml
          # @param sTimeStamp: url args timestampe or custom made or None
          # @param sNonce: url args nonce or custom made
          # sEncryptMsg: encrypted 'msg_signature, timestamp, nonce, encrypt'
          # return: Success -> 0. sEncryptMsg, faild return 'None'
          pc = Prpcrypt(self.key)
          ret, encrypt = pc.encrypt(sReplyMsg, self.appid)
          if ret != 0:
              return ret, None
          if timestamp is None:
              timestamp = str(int(time.time()))
          # Generating secure signatures
          sha1 = SHA1()
          ret, signature = sha1.getSHA1(self.token, timestamp, sNonce, encrypt)
          if ret != 0:
              return ret, None
          xmlParse = XMLParse()
          return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
  
      def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
          # Test the authenticity of the message,
          # and obtain the decrypted plaintext.
          # @param sMsgSignature: signature, match url args msg_signature
          # @param sTimeStamp: timestamp, match url args timestamp
          # @param sNonce: random string, match url args nonce
          # @param sPostData: Ciphertext, match post request body
          # xml_content: Original decrypted while request return 0
          # @return: Success 0, failure to return the error code
          # Secure signature verification
          xmlParse = XMLParse()
          ret, encrypt, touser_name = xmlParse.extract(sPostData)
          if ret != 0:
              return ret, None
          sha1 = SHA1()
          ret, signature = sha1.getSHA1(self.token, sTimeStamp, sNonce, encrypt)
          if ret != 0:
              return ret, None
          if not signature == sMsgSignature:
              return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
          pc = Prpcrypt(self.key)
          ret, xml_content = pc.decrypt(encrypt, self.appid)
          return ret, xml_content
  