最新消息:20210917 已从crifan.com换到crifan.org

【已解决】Python的Flask中解密微信小程序返回的加密的用户信息

微信 crifan 667浏览 0评论
折腾:
【记录】设计测评系统的用户注册接口
期间,需要对于微信小程序接口:
用户信息 · 小程序
返回的加了密的用户信息,去解密。
目前调试期间,拿到了返回的信息:
{
  "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中。
先去参考官网解释:
https://developers.weixin.qq.com/miniprogram/dev/api/wx.getUserInfo.html
开放数据校验与解密 · 小程序
-》去下载demo代码
结果发现还缺少session_key:
然后再去研究如何获取到sessionKey
发现是:
wx.login · 小程序
得到:code
再去:
code2Session · 小程序
得到:session_key(和其他的openid,可能有的unionid)
期间可能会涉及到:
开放能力 · 小程序
目前看起来好像也不需要操心这部分逻辑
本地参考:
pycrypto · PyPI
先去安装解密的库:
➜  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
密码学 — The Hitchhiker’s Guide to Python
搜:
微信 decrypt UnicodeDecodeError
python3 解密运动步数报错 | 微信开放社区
解密时概率发生错误 · Issue #20 · gusibi/python-weixin
使用codecs自定义编/解码方案 | 小明明s à domicile | Python之美
http://www.dongwm.com/archives/使用codecs自定义编-解码方案/
[已解决]尝试使用明文模式去测试微信的Python版的SDK wechat-sdk – 在路上
那手动把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)
使用官方提供python demo解密 getPhoneNumber 数据时报错 | 微信开放社区
Cryptodome.Cipher.AES.MODE_CBC Python Example
微信wx.getUserInfo接口aes数据解密算法用openresty实现问题 – Google Groups
python和JS相互AES加解密 – 知乎
去试试:
decriptedDataUtf8 = decriptedData.decode(encoding="utf-8")
结果也是:
发生异常: UnicodeDecodeError
'utf-8' codec can't decode byte 0x88 in position 3: invalid start byte
python获取微信小程序手机号并绑定遇到的坑 – 码农教程
python 2 json.loads
18.2. json — JSON encoder and decoder — Python 2.7.15 documentation
“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().”
json — JSON encoder and decoder — Python 3.7.2 documentation
看来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)
JSON读取数据 ValueError: Extra data: line 2 column … – 简书
大概明白了:
{"pid": 150400, "id": 150402, "name": "电影票"}
{"pid": 150000, "id": 150500, "name": "票务"}
是无法解析的,报错:Extra data
表示json太多,不止一个json,无法继续。
-》需要只传入一个json去解析才行
ValueError: Extra Data error when importing json file using python – Stack Overflow
python – ValueError: Extra data: line 1 column 5 – line 1 column 2319 (char 4 – 2318) – Stack Overflow
Python json.loads shows ValueError: Extra data – Stack Overflow
微信 WXBizDataCrypt  exceptions.ValueError Extra data
Load报错:ValueError:Extra data,怎么解决? – 问答 – 云+社区 – 腾讯云
python和JS相互AES加解密 – 知乎
小程序  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,是参考:
code2Session · 小程序
去后台实现的接口:
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
即可。

转载请注明:在路上 » 【已解决】Python的Flask中解密微信小程序返回的加密的用户信息

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
99 queries in 0.198 seconds, using 23.32MB memory