上一篇文章手把手教會你小程序登陸鑑權介紹了小程序如何進行登陸鑑權,那麼通常小程序的用戶標識可使用上文所述微信提供的jscode2session
接口來換取,小程序還提供了一個getUserInfo
的API來獲取用戶數據,這個用戶數據裏面也能夠包含當前的用戶標識openid。本文就如何獲取小程序中的用戶數據及數據完整性校驗等內容來展開詳述javascript
wx.getUserInfo
是用來獲取用戶信息的API接口,下面是對應的參數字段:html
字段 | 類型 | 是否必填 |
---|---|---|
withCredentials | Boolean | 否 |
lang | String | 否 |
timeout | Number | 否 |
success | Function | 否 |
fail | Function | 否 |
complete | Function | 否 |
lang 指定返回用戶信息的語言,有三個值:java
timeout 指定API調用的超時時間, getUserInfo
API其實底層也是客戶端發起一個http請求,來獲取到用戶的相關數據,通過封裝後返回給小程序端,後面會給你們詳細介紹。node
withCredentials 這個字段是一個布爾類型的值,決定了在調用API時小程序返回的數據裏是否帶上登陸態信息,不填的話默認該字段的值爲true
git
那麼此時API返回的結果爲:github
字段 | 類型 | 描述 |
---|---|---|
encryptedData | String | 加密後的用戶數據 |
iv | String | 解密算法向量 |
rawData | String | 用戶開放數據 |
signature | String | 簽名 |
userInfo | Object | 用戶開放數據 |
若是該字段的值爲false
,就不會返回上面這兩個字段:encryptedData
, iv
。web
encryptedData 爲包括敏感數據在內的完整用戶信息的加密數據,敏感數據涉及到了用戶的openid
及unionid
等。那麼數據加密採用的算法爲AES-128-CBC
分組對稱加解密算法,後面咱們對這個加密算法進行詳細分析。算法
iv 爲上述解密算法的算法初始向量。一樣咱們在後面會詳細介紹。小程序
rawData 爲一個對象字符串,裏面包含了用戶的一些開放數據,分別是:nickName(微信暱稱)
、province(所屬省份)
、language(微信客戶端內設置的語言類型)
、gender(用戶性別)
、country(所在國家)
、city(所在城市)
、avatarUrl(微信頭像地址)
api
signature 爲了保證數據的有效性和安全性,小程序對明文數據進行了簽名。這個值是sha1(rawData + session_key)
計算後的值,sha1
則是一種密碼的哈希函數,相比於md5
哈希函數來講抗攻擊性更強。
userInfo 字段是一個對象,也是用戶開放數據,和rawData展現的內容一致,只不過rawData將對象序列化爲字符串做爲返回值。
前面給你們講到在客戶端內調用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_key
和openid
。
上面咱們說過,在小程序裏經過API獲取到的用戶完整信息encryptedData
,是須要經過AES-128-CBC
算法來加解密的。首先咱們先來了解什麼是AES-128-CBC
:
AES 全稱爲 Advanced Encryption Standard,是美國國家標準與技術研究院(NIST)在2001年創建了電子數據的加密規範,它是一種分組加密標準,每一個加密塊大小爲128位,容許的密鑰長度爲12八、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五種加密模式(CBC、ECB、CTR、OCF、CFB)
高級加密標準AES的工做模式(ECB、CBC、CFB、OFB)
《IVWEB 技術週刊》 震撼上線了,關注公衆號:IVWEB社區,每週定時推送優質文章。