折腾:
【记录】设计测评系统的用户注册接口
期间,需要对于微信小程序接口:
返回的加了密的用户信息,去解密。
目前调试期间,拿到了返回的信息:
{ "errMsg": "getUserInfo:ok", "rawData": "{\"nickName\":\"xxx\",\"gender\":0,\"language\":\"zh_CN\",\"city\":\"Suzhou\",\"province\":\"Jiangsu\",\"country\":\"China\",\"avatarUrl\":\"https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTIDnEfia1xxxEMF10a8oIy5Q/132\"}", "userInfo": { "nickName": "xxx", "gender": 0, "language": "zh_CN", "city": "Suzhou", "province": "Jiangsu", "country": "China", "avatarUrl": "https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTxxxxIy5Q/132" }, "signature": "85e0cxxx6e", "encryptedData": "HvaWTenZDqMXkt+7xxxx6Dk=", "iv": "CKxxxHw==" }
现在需要想办法,先去调试出解密,然后再去合并到Flask中。
先去参考官网解释:
结果发现还缺少session_key:

然后再去研究如何获取到sessionKey
发现是:
得到:code
再去:
得到:session_key(和其他的openid,可能有的unionid)
期间可能会涉及到:
目前看起来好像也不需要操心这部分逻辑
本地参考:
先去安装解密的库:
➜ crifanLib git:(master) ✗ pip3 install pycrypto Collecting pycrypto Downloading https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz (446kB) 100% |████████████████████████████████| 450kB 56kB/s Building wheels for collected packages: pycrypto Running setup.py bdist_wheel for pycrypto ... done Stored in directory: /Users/crifan/Library/Caches/pip/wheels/27/02/5e/77a69d0c16bb63c6ed32f5386f33a2809c94bd5414a2f6c196 Successfully built pycrypto Installing collected packages: pycrypto Successfully installed pycrypto-2.6.1 You are using pip version 10.0.1, however version 18.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command.
再去调试代码:
import base64 import json from Crypto.Cipher import AES class WXBizDataCrypt: def __init__(self, appId, sessionKey): self.appId = appId self.sessionKey = sessionKey def decrypt(self, encryptedData, iv): # base64 decode sessionKey = base64.b64decode(self.sessionKey) encryptedData = base64.b64decode(encryptedData) iv = base64.b64decode(iv) cipher = AES.new(sessionKey, AES.MODE_CBC, iv) decriptedData = cipher.decrypt(encryptedData) unpadedData = self._unpad(decriptedData) decrypted = json.loads(unpadedData) if decrypted['watermark']['appid'] != self.appId: raise Exception('Invalid Buffer') return decrypted def _unpad(self, s): return s[:-ord(s[len(s)-1:])] def testdecryptWechatInfo(): appId = 'wxxxxb0' sessionKey = 'Qxx=' encryptedData = 'Hvxxx=' iv = 'xxx=' pc = WXBizDataCrypt(appId, sessionKey) decryptedInfo = pc.decrypt(encryptedData, iv) print("decryptedInfo=%s" % decryptedInfo) if __name__ == '__main__': print("[crifanLib-%s] %s" % (CURRENT_LIB_FILENAME, __version__)) testdecryptWechatInfo()
结果报错:

发生异常: UnicodeDecodeError 'utf-8' codec can't decode byte 0x88 in position 3: invalid start byte
去看看,发现是json.loads,输入的是bytes,而不是希望的str
搜:
微信 decrypt UnicodeDecodeError
使用codecs自定义编/解码方案 | 小明明s à domicile | Python之美
http://www.dongwm.com/archives/使用codecs自定义编-解码方案/
那手动把binary变成字符串?
主动从python3换成python2试试?
先去:
pip install pycrypto
➜ crifanLib git:(master) ✗ pip install pycrypto --user Collecting pycrypto matplotlib 1.3.1 requires nose, which is not installed. matplotlib 1.3.1 requires tornado, which is not installed. pyopenssl 18.0.0 has requirement six>=1.5.2, but you'll have six 1.4.1 which is incompatible. Installing collected packages: pycrypto Successfully installed pycrypto-2.6.1 You are using pip version 10.0.1, however version 18.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command.
结果也还是报错啊:

发生异常: exceptions.ValueError Extra data: line 1 column 2 - line 1 column 351 (char 1 - 350) File "/Users/crifan/dev/dev_root/crifan/CrifanLib/crifanLib/python/crifanLib/crifanWechat.py", line 61, in decrypt File "/Users/crifan/dev/dev_root/crifan/CrifanLib/crifanLib/python/crifanLib/crifanWechat.py", line 117, in testdecryptWechatInfo File "/Users/crifan/dev/dev_root/crifan/CrifanLib/crifanLib/python/crifanLib/crifanWechat.py", line 123, in <module>
试试:
encryptedData = str('HvaWTenxxxxxxcQC6InXm6Dk=')
问题依旧。
去看看:
encryptedData = base64.b64decode(encryptedData)
好像是把 str转换成binary data了
encryptedData = base64.b64decode(encryptedData)
去试试:
decriptedDataUtf8 = decriptedData.decode(encoding="utf-8")
结果也是:
发生异常: UnicodeDecodeError 'utf-8' codec can't decode byte 0x88 in position 3: invalid start byte
python 2 json.loads
“json.loads(s[, encoding[, cls[, object_hook[, parse_float[, parse_int[, parse_constant[, object_pairs_hook[, **kw]]]]]]]])
Deserialize s (a str or unicode instance containing a JSON document) to a Python object using this conversion table.
If s is a str instance and is encoded with an ASCII based encoding other than UTF-8 (e.g. latin-1), then an appropriate encodingname must be specified. Encodings that are not ASCII based (such as UCS-2) are not allowed and should be decoded to unicodefirst.
The other arguments have the same meaning as in load().”
看来python2中json.loads传入 str或unicode 都可以的
且说了,如果编码不是UTF-8,则要指定编码
python2的:
decrypted = json.loads(unpadedData)
还是出错:

发生异常: exceptions.ValueError Extra data: line 1 column 2 - line 1 column 351 (char 1 - 350) File "/Users/crifan/dev/dev_root/crifan/CrifanLib/crifanLib/python/crifanLib/crifanWechat.py", line 64, in decrypt File "/Users/crifan/dev/dev_root/crifan/CrifanLib/crifanLib/python/crifanLib/crifanWechat.py", line 120, in testdecryptWechatInfo File "/Users/crifan/dev/dev_root/crifan/CrifanLib/crifanLib/python/crifanLib/crifanWechat.py", line 126, in <module>
exceptions.ValueError
Extra data: line 1 column 2 – line 1 column 351 (char 1 – 350)
exceptions.ValueError Extra data: line 1 column 2 – line 1 column 351 (char 1 – 350)
大概明白了:
{"pid": 150400, "id": 150402, "name": "电影票"} {"pid": 150000, "id": 150500, "name": "票务"}
是无法解析的,报错:Extra data
表示json太多,不止一个json,无法继续。
-》需要只传入一个json去解析才行
微信 WXBizDataCrypt exceptions.ValueError Extra data
小程序 WXBizDataCrypt exceptions.ValueError Extra data
此处解密后数据是:
'8\x1c\x1c\x88\x93\xc9\xdbj]t\xc6$\xa1\xe94\xfd\xbc\xdd4\xebr\xdcj1Nmq\x8d\xea;Dh\x97\xd5\xc8W\xf1\xdac\x07\x11wZ\xae\x80seN\x95\x07;\xa4\x05?O\xea\xd9#t\x14w`OG\xca]\x95\xc3\xcb\xdbX\xf9\x0c \xe9J\xb8\x13\xf9\x9b\x1e\xa6`\x01\x19\xab!\x05H\xa3T\xf0O0\x05b[\xf3I\xfe^_\x10\xc5\xa0?u\'\x85\xdb\x86/\x06\x1dvF\xfe\x1dSi\xc1=\xbf\x10\xa7\xbax\xeb\xf6\x0ci<`\xf7\xcfJ\xc3\xf3e\xe9\x04\xc5\x0e\x84\xabsVm\x94\xfb\xfd\xd41\xa4\x1f"b\xba\xc1\x02\x96@+\xff;H\xa5\x03\xcd\x8b\xb6\xa1x\xd4\x19\x10\xc6\xeabf\x00\x9a\x90\xd6\xd6&\x15\xc4\xe0\x04\x05}\x84\xe9H_\xef\xf9\xdfs\x16f5\xf5\xcb\r\x1c\xfb5\x95\xc1\x97X6ZF\x0e\x16\x0e~\xf8\x13\xdd\x03\xbc\x87_\x8c\x9c$4\x10U\xa9\x82\xc5\x14&\x10\xacDF0\xbe%(\xc5\xca\xec\x07\x9e\xd06\xe5M\x1cH\xe1U.(\xec\xae\x0b\x83mc\xc7\xd5xn\'\x01\x11X4\xa2\xd6\xb8\x16\xa8K\xe3\xcd\xdf\xd8,\x9a\xc1.oJ\xbb\xc9\x1bg\x96\xad\xdbK\xad\\\x8b\xf2(\xcc\xbe\x0ck\xf6.\xd6V\x84\x16\xa0\xd9\xfe\xd2X=\xcaX\x8b\xdaMxD[`>u\x8e\x05\xb60\x1df\xa3\xe5\xa6\xb0\t\xff\xa1b\xbeJ\x05d[\x04?9\x82aX\xbe<\x16e\xcdry\xd5Y\x94\x12'
去处理看看,能否找出多个json的位置

也看不出什么规律
然后官方代码是可以正常解码的


突然想起来了:
难道是此处的 encryptedData和 (通过code获取到的)sessionKey
是不匹配的,导致获取到的数据有问题,导致无法json.loads
后来去换成最新的:
- sessionKey
- 从最新的code中调接口获取的
- encryptedData
- iv
果然是可以正常解密的:

至此,算是正常可以解密数据了。
再去换成python3,也是正常的:

所以,此处也不用弄什么库,只是完整代码是:
import base64 import json from Crypto.Cipher import AES class WXBizDataCrypt: def __init__(self, appId, sessionKey): self.appId = appId self.sessionKey = sessionKey def decrypt(self, encryptedData, iv): # base64 decode sessionKey = base64.b64decode(self.sessionKey) encryptedData = base64.b64decode(encryptedData) iv = base64.b64decode(iv) cipher = AES.new(sessionKey, AES.MODE_CBC, iv) decriptedData = cipher.decrypt(encryptedData) unpadedData = self._unpad(decriptedData) decrypted = json.loads(unpadedData) if decrypted['watermark']['appid'] != self.appId: raise Exception('Invalid Buffer') return decrypted def _unpad(self, s): return s[:-ord(s[len(s)-1:])] appId = 'wx1xxx0' sessionKey = 'lcxxxxx==' encryptedData = "xxxxxx = "eTxxxMQ==" pc = WXBizDataCrypt(appId, sessionKey) decryptedInfo = pc.decrypt(encryptedData, iv) print("decryptedInfo=%s" % decryptedInfo)
其中的code,是参考:
去后台实现的接口:
class WechatCode2SessionAPI(Resource): def get(self): log.info("WechatCode2Session GET") respDict = { "code": 200, "message": "Code to session ok", "data": {} } parser = reqparse.RequestParser() parser.add_argument('appid', type=str, help="wechat appid") parser.add_argument('secret', type=str, help="wechat app secret") parser.add_argument('js_code', type=str, help="wechat code, return from get") parser.add_argument('grant_type', type=str, help="wechat only support authorization_code") parsedArgs = parser.parse_args() # log.debug("parsedArgs=%s", parsedArgs) if not parsedArgs: respDict["code"] = 404 respDict["message"] = "Fail to parse input parameters" return jsonify(respDict) appId = parsedArgs["appid"] appSecret = parsedArgs["secret"] jsCode = parsedArgs["js_code"] grantType = parsedArgs["grant_type"] # log.debug("appId=%s, appSecret=%s, jsCode=%s, grantType=%s", appId, appSecret, jsCode, grantType) log.debug("appId=%s, jsCode=%s, grantType=%s", appId, jsCode, grantType) # https://developers.weixin.qq.com/miniprogram/dev/api/code2Session.html # GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code reqParams = { "appid": appId, "secret": appSecret, "js_code": jsCode, "grant_type": grantType } # log.debug("reqParams=%s", reqParams) jscode2sessionUrl = "https://api.weixin.qq.com/sns/jscode2session" respInfo = requests.get(jscode2sessionUrl, params=reqParams) respJson = respInfo.json() # normal: {'session_key': 'QTxxxxl/5h/Q==', 'openid': 'o7xxxBY'} # code expired: {'errcode': 40029, 'errmsg': 'invalid code, hints: [ req_id: 03768689 ]'} # code has been used: {'errcode': 40163, 'errmsg': 'code been used, hints: [ req_id: JV7LHa06718595 ]'} log.debug("respJson=%s", respJson) respDict["data"] = respJson return jsonify(respDict)

然后加到Flask项目中。
【总结】
最后代码是:
import base64 import json from Crypto.Cipher import AES #---------------------------------------- # Wechat Functions #---------------------------------------- class WXBizDataCrypt: def __init__(self, appId, sessionKey): self.appId = appId self.sessionKey = sessionKey def decrypt(self, encryptedData, iv): # base64 decode sessionKey = base64.b64decode(self.sessionKey) encryptedData = base64.b64decode(encryptedData) iv = base64.b64decode(iv) cipher = AES.new(sessionKey, AES.MODE_CBC, iv) decriptedData = cipher.decrypt(encryptedData) unpadedData = self._unpad(decriptedData) decrypted = json.loads(unpadedData) if decrypted['watermark']['appid'] != self.appId: raise Exception('Invalid Buffer') return decrypted def _unpad(self, s): return s[:-ord(s[len(s)-1:])] def decryptWechatInfo(appId, sessionKey, encryptedData, iv): """ decrypt wechat info, return from https://developers.weixin.qq.com/miniprogram/dev/api/wx.getUserInfo.html """ cryptObj = WXBizDataCrypt(appId, sessionKey) decryptedInfo = cryptObj.decrypt(encryptedData, iv) return decryptedInfo
即可。