小程序中神秘的用户数据

上一篇文章 手把手教会你小程序登录鉴权 介绍了小程序如何进行登录鉴权,那么一般小程序的用户标识可以使用上文所述微信提供的 jscode2session 接口来换取,小程序还提供了一个 getUserInfo 的API来获取用户数据,这个用户数据里面也可以包含当前的用户标识openid。本文就 如何获取小程序中的用户数据及数据完整性校验 等内容来展开详述

API介绍

wx.getUserInfo 是用来获取用户信息的API接口,下面是对应的参数字段:

lang 指定返回用户信息的语言,有三个值:lang

  • zh_CN 简体中文
  • zh_TW 繁体中文
  • en 英文,默认为en

timeout

timeout 指定API调用的超时时间, getUserInfo API其实底层也是客户端发起一个http请求,来获取到用户的相关数据,经过封装后返回给小程序端,后面会给大家详细介绍。

withCredentials

withCredentials 这个字段是一个布尔类型的值,决定了在调用API时小程序返回的数据里是否带上登录态信息,不填的话默认该字段的值为 true

那么此时API返回的结果为:

encryptedData 为包括敏感数据在内的完整用户信息的加密数据,敏感数据涉及到了用户的 openidunionid 等。那么数据加密采用的算法为 AES-128-CBC 分组对称加解密算法,后面我们对这个加密算法进行详细分析。如果该字段的值为 false ,就不会返回上面这两个字段: encryptedData , iv

  • iv 为上述解密算法的算法初始向量。同样我们在后面会详细介绍。
  • rawData 为一个对象字符串,里面包含了用户的一些开放数据,分别是: nickName(微信昵称)province(所属省份)language(微信客户端内设置的语言类型)gender(用户性别)country(所在国家)city(所在城市)avatarUrl(微信头像地址)
  • signature 为了保证数据的有效性和安全性,小程序对明文数据进行了签名。这个值是 sha1(rawData + session_key) 计算后的值, sha1 则是一种密码的哈希函数,相比于 md5 哈希函数来说抗攻击性更强。
  • userInfo 字段是一个对象,也是用户开放数据,和rawData展示的内容一致,只不过rawData将对象序列化为字符串作为返回值。

API之http请求

前面给大家讲到在客户端内调用 getUserInfo API时,微信客户端会向微信服务端发送一条请求,在微信开发者工具里通过 http请求抓包可以看到,发出了一条 https://servicewechat.com/wxa-dev-logic/jsoperatewxdata 这样的http请求。

请求体里携带了几个重要的参数,包括 data , grant_type 等,data字段是一个JSON字符串,里面有一个字段 api_name ,其值为'webapi_userinfo'。而grant_type字段也对应了一个值“webapi_userinfo”。

响应体返回了一个JSON对象,首先是一个 baseresponse 字段,里面包含了接口调用的返回码 errcode 和调用结果 errmsg 。该对象还返回了一个 data 字段,这个data字段对应了一个JSON字符串,里面就是通过调用API拿到的所有用户数据信息。在开发者工具内,我们还可以看到返回了一个 debug_info 字段,这个里面同样包含了用户的数据 data ,只不过这里的 data 还返回了用户的openid,同时还返回了用户的session_key登录态凭据。

一般我们可以在开发者工具内通过抓包,来调试一些信息的有效性,包括用户的 session_keyopenid

AES-128-CBC 加密算法

上面我们说过,在小程序里通过API获取到的用户完整信息 encryptedData ,是需要通过 AES-128-CBC 算法来加解密的。首先我们先来了解什么是 AES-128-CBC

AES 全称为 Advanced Encryption Standard,是美国国家标准与技术研究院(NIST)在2001年建立了电子数据的加密规范,它是一种分组加密标准,每个加密块大小为128位,允许的密钥长度为128、192和256位。

分组加密有五种模式,分别是

ECB(Electronic Codebook Book) 电码本模式

CBC(Cipher Block Chaining) 密码分组链接模式

CTR(Counter) 计算器模式

CFB(Cipher FeedBack) 密码反馈模式

OFB(Output FeedBack) 输出反馈模式

这里我们主要来看 AES-128-CBC 的分组加密算法,即用同一组key进行明文和密文的转换,以128bit为一组,128bit也就是16byte,那么明文的每16字节为一组就对应了加密后的16字节的密文。如果最后剩余的明文不够16字节时,就需要进行填充了,通常会采用 PKCS#7 (PKCS#5仅支持填充8字节的数据块,而PKCS#7支持1-255之间的字节块)来进行填充。

如果最后剩余的明文为13个字节,也就是缺少了3个字节才能为一组,那么这个时候就需要填充3个字节的0x03:

明文数据:   05 05 05 05 05 05 05 05 05 05 05 05 05
PKCS#7填充: 05 05 05 05 05 05 05 05 05 05 05 05 05 03 03 03

若明文正好是16个字节的整数倍,最后要再加入一个16字节0x10的组再进行加密。

因此,我们发现PKCS#7填充的两个特点:

  • 填充的字节都是一个相同的字节
  • 该字节的值,就是要填充的字节的个数

我们再来一起看明文加密的过程,CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文进行异或运算,然后将得到的结果再通过加密器加密,其中第一个密码块会与我们前文所述的 iv初始化向量 的数据块进行异或运算。如下图(图片来自wiki百科)

但是需要明确说明的是,这里API返回的 iv 是解密算法对应的初始化向量,而非加密算法对应的初始化向量。所以大家肯定也就猜到了,CBC模式解密时第一个密码块也是需要和初始化向量进行异或运算的。如下图(图片来自wiki百科):

在小程序里,这里加密和解密的密码器为我们上一篇文章所获取到的经过base64编码的 session_key

小程序中的应用

那么在前面我们大致了解了小程序中是如何对用户数据进行加密的之后,我们就一起以nodejs为例来看看如何在服务端对用户数据进行解密,以及解密后的数据完整性校验:

在util.js文件中,定义了两个方法:

decryptByAES 方法是利用服务端在登录时通过微信提供的 jscode2session 接口拿到的 session_key 和调用wx.getUserInfo后将返回的 iv 初始化向量来解密 encryptedData

encryptedBySha1 方法是通过 sha1 哈希算法来加密 session_key 生成小程序应用自身的用户登录态标识,保证 session_key 的安全性。

// util.js
const crypto = require('crypto');
module.exports = {
    decryptByAES: function (encrypted, key, iv) {
        encrypted = new Buffer(encrypted, 'base64');
        key = new Buffer(key, 'base64');
        iv = new Buffer(iv, 'base64');
        const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
        let decrypted = decipher.update(encrypted, 'base64', 'utf8')
        decrypted += decipher.final('utf8');
        return decrypted
    },
    encryptBySha1: function (data) {
        return crypto.createHash('sha1').update(data, 'utf8').digest('hex')
    }
};

在auth.js文件中,调用了上篇文章里的 getSessionKey 方法,获取用户的 openidsession_key ,拿到这两者后,对加密的用户数据进行解密操作,同时将解密后的用户数据及用户的session_key和skey存入数据表中。

这里需要注意到一点:如果当前小程序绑定了开放平台的移动应用或网站应用,或公众平台的公众号等,那么 encryptedData 还会多返回一个 unionId 的字段,这个unionId可在小程序和其他已绑定的平台之间区分用户的唯一性,也就是说 同一用户,对同一个微信开放平台下的不同应用,unionid是相同的 。一般,我们可以用unionId来打通小程序和其他应用之间的用户登录态。

// auth.js
const { decryptByAES, encryptBySha1 } = require('../util');
return getSessionKey(code, appid, secret)
    .then(resData => {
        // 选择加密算法生成自己的登录态标识
        const { session_key } = resData;
        const skey = encryptBySha1(session_key);

        let decryptedData = JSON.parse(decryptByAES(encryptedData, session_key, iv));
        // 存入用户数据表中
        return saveUserInfo({
            userInfo: decryptedData,
            session_key,
            skey
        })
    })
    .catch(err => {
        return {
            result: -10003,
            errmsg: JSON.stringify(err)
        }
    })

校验数据完整性和有效性

当我们通过解密拿到用户的完整数据后,可以对拿到的数据进行数据的完整性和有效性校验,防止用户数据被恶意篡改。这里说明如何进行相关的数据校验:

有效性校验:在前面我们介绍到,当 withCredentials 设置为true时,返回的数据还会带上一个 signature 的字段,其值是 sha1(rawData + session_key) 的结果,开发者可以将所拿到的signature,在自己服务端使用相同的sha1算法算出对应的signature2,即

signature2 = encryptedBySha1(rawData + session_key);

通过对比signature与signature2是否一致,来确定用户数据的完整性。

完整性校验:在前面拿到的 encryptedData 并进行相关解密操作后,会看到用户数据的object对象里存在一个 watermark 的字段,官方称之为数据水印,这个字段结构为:

"watermark": {
    "appid":"APPID",
    "timestamp":TIMESTAMP
}

这里开发同学可以校验watermark内的appid和自身appid是否一致,以及watermark内的数据获取的timestamp时间戳,来校验数据的时效性。

作者:腾讯IVWEB团队
链接:小程序中神秘的用户数据-教程-小程序社区-微信小程序-微信小程序开发社区-小程序开发论坛-微信小程序联盟
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容