小程序中神秘的用户数据
前面
上一篇文章手把手教會你小程序登錄鑒權介紹了小程序如何進行登錄鑒權,那么一般小程序的用戶標識可以使用上文所述微信提供的jscode2session接口來換取,小程序還提供了一個getUserInfo的API來獲取用戶數據,這個用戶數據里面也可以包含當前的用戶標識openid。本文就如何獲取小程序中的用戶數據及數據完整性校驗等內容來展開詳述
API介紹
wx.getUserInfo是用來獲取用戶信息的API接口,下面是對應的參數字段:
| withCredentials | Boolean | 否 |
| lang | String | 否 |
| timeout | Number | 否 |
| success | Function | 否 |
| fail | Function | 否 |
| complete | Function | 否 |
lang
lang 指定返回用戶信息的語言,有三個值:
- zh_CN 簡體中文
- zh_TW 繁體中文
- en 英文,默認為en
timeout
timeout 指定API調用的超時時間, getUserInfoAPI其實底層也是客戶端發起一個http請求,來獲取到用戶的相關數據,經過封裝后返回給小程序端,后面會給大家詳細介紹。
withCredentials
withCredentials 這個字段是一個布爾類型的值,決定了在調用API時小程序返回的數據里是否帶上登錄態信息,不填的話默認該字段的值為true
那么此時API返回的結果為:
| encryptedData | String | 加密后的用戶數據 |
| iv | String | 解密算法向量 |
| rawData | String | 用戶開放數據 |
| signature | String | 簽名 |
| userInfo | Object | 用戶開放數據 |
如果該字段的值為false,就不會返回上面這兩個字段:encryptedData, iv。
-
encryptedData 為包括敏感數據在內的完整用戶信息的加密數據,敏感數據涉及到了用戶的openid及unionid等。那么數據加密采用的算法為AES-128-CBC分組對稱加解密算法,后面我們對這個加密算法進行詳細分析。
-
iv 為上述解密算法的算法初始向量。同樣我們在后面會詳細介紹。
-
rawData 為一個對象字符串,里面包含了用戶的一些開放數據,分別是:nickName(微信昵稱)、province(所屬省份)、language(微信客戶端內設置的語言類型)、gender(用戶性別)、country(所在國家)、city(所在城市)、avatarUrl(微信頭像地址)
-
signature 為了保證數據的有效性和安全性,小程序對明文數據進行了簽名。這個值是sha1(rawData + session_key)計算后的值,sha1則是一種密碼的哈希函數,相比于md5哈希函數來說抗攻擊性更強。
-
userInfo 字段是一個對象,也是用戶開放數據,和rawData展示的內容一致,只不過rawData將對象序列化為字符串作為返回值。
API之http請求
前面給大家講到在客戶端內調用getUserInfoAPI時,微信客戶端會向微信服務端發送一條請求,在微信開發者工具里通過 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_key和openid。
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方法,獲取用戶的openid和session_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時間戳,來校驗數據的時效性。
最后
那么上面就是小程序中如何對用戶數據進行加解密操作,以及如何對用戶數據進行相關處理和校驗的介紹,請大家多多指教!
參考文章:
密碼算法詳解——AES
AES五種加密模式(CBC、ECB、CTR、OCF、CFB)
對加密算法 AES-128-CBC 的一些理解
高級加密標準AES的工作模式(ECB、CBC、CFB、OFB)
《IVWEB 技術周刊》 震撼上線了,關注公眾號:IVWEB社區,每周定時推送優質文章。
- 周刊文章集合: weekly
- 團隊開源項目: Feflow
總結
以上是生活随笔為你收集整理的小程序中神秘的用户数据的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java学习之模拟纸牌游戏,List的A
- 下一篇: 使用 MarkDown DocFX 升