公眾平台消息體簽名及加解密方案概述
1.新增消息體簽名驗證,用於公眾平台和公眾賬號驗證消息體的正確性
2.針對推送給微信公眾賬號的普通消息和事件消息,以及推送給設備公眾賬號的設備消息進行加密
3.公眾賬號對密文消息的回復也要求加密
開發者需注意,公眾賬號主動調用API的情況將不受影響。
啟用加解密功能(即選擇兼容模式或安全模式)后,公眾平台服務器在向公眾賬號服務器配置地址(可在“開發者中心”修改)推送消息時,URL將新增加兩個參數(加密類型和消息體簽名),並以此來體現新功能。加密算法采用AES,具體的加解密流程和方案請看接入指引、技術方案和示例代碼。
為了配合消息加密功能的上線,並幫助開發者適配新特性,公眾平台提供了3種加解密的模式供開發者選擇,即明文模式、兼容模式、安全模式(可在“開發者中心”選擇相應模式),選擇兼容模式和安全模式前,需在開發者中心填寫消息加解密密鑰EncodingAESKey。
明文模式:維持現有模式,沒有適配加解密新特性,消息體明文收發,默認設置為明文模式
兼容模式:公眾平台發送消息內容將同時包括明文和密文,消息包長度增加到原來的3倍左右;公眾號回復明文或密文均可,不影響現有消息收發;開發者可在此模式下進行調試
安全模式(推薦):公眾平台發送消息體的內容只含有密文,公眾賬號回復的消息體也為密文,建議開發者在調試成功后使用此模式收發消息
什么是EncodingAESKey? 微信公眾平台采用AES對稱加密算法對推送給公眾帳號的消息體對行加密,EncodingAESKey則是加密所用的秘鑰。公眾帳號用此秘鑰對收到的密文消息體進行解密,回復消息體也用此秘鑰加密。
此外,微信公眾平台為開發者提供了5種語言的示例代碼(包括C++、php、Java、Python和C#版本,http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip )。 請開發者查看接入指引和開發者FAQ來接入消息體簽名及加解密功能,若關注技術實現,可查看技術方案。
該文檔講述如何使用示例代碼接入加解密,參考本文檔並使用示例代碼,加解密的接入將非常簡單。若想進一步的了解細節,請查看技術方案。 微信公眾平台提供了C++、php、Java、Python和C# 5種語言的示例代碼,每種語言的類名和接口名均一致,下面以C++為例說明:
目錄[隱藏] |
函數說明
構造函數
// @param sToken: 公眾平台上,開發者設置的Token // @param sEncodingAESKey: 公眾平台上,開發者設置的EncodingAESKey // @param sAppid: 公眾號的appid WXBizMsgCrypt(const std::string &sToken, const std::string &sEncodingAESKey, const std::string &sAppid);
解密函數
// 檢驗消息的真實性,並且獲取解密后的明文 // @param sMsgSignature: 簽名串,對應URL參數的msg_signature // @param sTimeStamp: 時間戳,對應URL參數的timestamp // @param sNonce: 隨機串,對應URL參數的nonce // @param sPostData: 密文,對應POST請求的數據 // @param sMsg: 解密后的明文,當return返回0時有效 // @return: 成功0,失敗返回對應的錯誤碼 int DecryptMsg(const std::string &sMsgSignature, const std::string &sTimeStamp, const std::string &sNonce, const std::string &sPostData, std::string &sMsg);
加密函數
//將公眾號回復用戶的消息加密打包 // @param sReplyMsg:公眾號待回復用戶的消息,xml格式的字符串 // @param sTimeStamp: 時間戳,可以自己生成,也可以用URL參數的timestamp // @param sNonce: 隨機串,可以自己生成,也可以用URL參數的nonce // @param sEncryptMsg: 加密后的可以直接回復用戶的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,當return返回0時有效 // return:成功0,失敗返回對應的錯誤碼 int EncryptMsg(const std::string &sReplyMsg, const std::string &sTimeStamp, const std::string &sNonce, std::string &sEncryptMsg);
使用方法
在安全模式或兼容模式下,url上會新增兩個參數encrypt_type和msg_signature。encrypt_type表示加密類型,msg_signature:表示對消息體的簽名。 url上無encrypt_type參數或者其值為raw時表示為不加密;encrypt_type為aes時,表示aes加密(暫時只有raw和aes兩種值)。公眾帳號開發者根據此參數來判斷微信公眾平台發送的消息是否加密。
兼容模式和安全模式加解密的方法完全一樣,兼容模式的xml消息體比安全模式多了幾個明文字段,具體請查看《消息加解密詳細技術方案》。
實例化對象
使用構造函數,實例化一個對象,傳入公眾帳號的token, appid, EncodingAESKey。
解密
安全模式或者兼容模式下,公眾號收到以下帶密文消息體(“……”表示兼容模式下的明文字段):
encrypt_msg = <xml> <ToUserName><![CDATA[gh_10f6c3c3ac5a]]></ToUserName> …… <Encrypt><![CDATA[hQM/NS0ujPGbF+/8yVe61E3mUVWVO1izRlZdyv26zrVUSE3zUEBdcXITxjbjiHH38kexVdpQLCnRfbrqny1yGvgqqKTGKxJWWQ9D5WiiUKxavHRNzYVzAjYkp7esNGy7HJcl/P3BGarQF3+AWyNQ5w7xax5GbOwiXD54yri7xmNMHBOHapDzBslbnTFiEy+8sjSl4asNbn2+ZVBpqGsyKDv0ZG+DlSlXlW+gNPVLP+YxeUhJcyfp91qoa0FJagRNlkNul4mGz+sZXJs0WF7lPx6lslDGW3J66crvIIx/klpl0oa/tC6n/9c8OFQ9pp8hrLq7B9EaAGFlIyz5UhVLiWPN97JkL6JCfxVooVMEKcKRrrlRDGe8RWVM3EW/nxk9Ic37lYY5j97YZfq375AoTBdGDtoPFZsvv3Upyut1i6G0JRogUsMPlyZl9B8Pl/wcA7k7i4LYMr2yK4SxNFrBUw==]]></Encrypt> </xml>
調用DecryptMsg接口,傳入收到的url上的參數:msg_signature(注意:不是signature,而是msg_signature), timestamp, nonce和encrypt_msg,若調用成功,sMsg則為輸出結果,其內容為如下的明文的xml消息體:
<xml> <ToUserName><![CDATA[gh_10f6c3c3ac5a]]></ToUserName> <FromUserName><![CDATA[oyORnuP8q7ou2gfYjqLzSIWZf0rs]]></FromUserName> <CreateTime>1411035097</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test message]]></Content> <MsgId>6060349595123187712</MsgId> </xml>
公眾帳號處理消息
生成需要回復給微信公眾平台的xml消息體,假設回復以下內容:
res_msg = <xml> <ToUserName><![CDATA[oyORnuP8q7ou2gfYjqLzSIWZf0rs]]></ToUserName> <FromUserName><![CDATA[gh_10f6c3c3ac5a]]></FromUserName> <CreateTime>1411034505</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[Welcome to join us!]]></Content> <FuncFlag>0</FuncFlag> </xml>
回包加密
調用EncryptMsg接口,傳入需要回復給微信公眾平台的res_msg, timestamp, nonce,若加密成功,則sEncryptMsg為密文消息體,內容如下:
<xml> <Encrypt><![CDATA[LDFAmKFr7U/RMmwRbsR676wjym90byw7+hhh226e8bu6KVYy00HheIsVER4eMgz/VBtofSaeXXQBz6fVdkN2CzBUaTtjJeTCXEIDfTBNxpw/QRLGLqqMZHA3I+JiBxrrSzd2yXuXst7TdkVgY4lZEHQcWk85x1niT79XLaWQog+OnBV31eZbXGPPv8dZciKqGo0meTYi+fkMEJdyS8OE7NjO79vpIyIw7hMBtEXPBK/tJGN5m5SoAS6I4rRZ8Zl8umKxXqgr7N8ZOs6DB9tokpvSl9wT9T3E62rufaKP5EL1imJUd1pngxy09EP24O8Th4bCrdUcZpJio2l11vE6bWK2s5WrLuO0cKY2GP2unQ4fDxh0L4ePmNOVFJwp9Hyvd0BAsleXA4jWeOMw5nH3Vn49/Q/ZAQ2HN3dB0bMA+6KJYLvIzTz/Iz6vEjk8ZkK+AbhW5eldnyRDXP/OWfZH2P3WQZUwc/G/LGmS3ekqMwQThhS2Eg5t4yHv0mAIei07Lknip8nnwgEeF4R9hOGutE9ETsGG4CP1LHTQ4fgYchOMfB3wANOjIt9xendbhHbu51Z4OKnA0F+MlgZomiqweT1v/+LUxcsFAZ1J+Vtt0FQXElDKg+YyQnRCiLl3I+GJ/cxSj86XwClZC3NNhAkVU11SvxcXEYh9smckV/qRP2Acsvdls0UqZVWnPtzgx8hc8QBZaeH+JeiaPQD88frNvA==]]></Encrypt> <MsgSignature><![CDATA[8d9521e63f84b2cd2e0daa124eb7eb0c34b6204a]]></MsgSignature> <TimeStamp>1411034505</TimeStamp> <Nonce><![CDATA[1351554359]]></Nonce> </xml>
注意事項
- EncodingAESKey長度固定為43個字符,從a-z,A-Z,0-9共62個字符中選取。 公眾帳號可以在公眾平台的開發者中心的服務器配置修改
- 出於安全考慮,公眾平台網站提供了修改EncodingAESKey的功能(在EncodingAESKey可能泄漏時進行修改),所以建議公眾賬號保存當前的和上一次的EncodinAESKey,若當前EncodingAESKey解密失敗,則嘗試用上一次的EncodingAESKey的解密。回包時,用哪個Key解密成功,則用此Key加密對應的回包
- 兼容模式消息體同時存在明文和密文,消息體會增至以前的3倍左右,開發者注意檢查系統,防止因消息變長和URL參數增加而出現接收錯誤
- 如果url上無encrypt_type參數或者其值為raw,則回復明文,否則回復密文。兼容模式期間公眾賬號回復明文或密文均可(不要兩種類型都回)
函數錯誤返回碼
函數返回碼 | 說明 |
---|---|
0 | 處理成功 |
-40001 | 校驗簽名失敗 |
-40002 | 解析xml失敗 |
-40003 | 計算簽名失敗 |
-40004 | 不合法的AESKey |
-40005 | 校驗AppID失敗 |
-40006 | AES加密失敗 |
-40007 | AES解密失敗 |
-40008 | 公眾平台發送的xml不合法 |
-40009 | Base64編碼失敗 |
-40010 | Base64解碼失敗 |
-40011 | 公眾帳號生成回包xml失敗 |
示例代碼下載
微信公眾平台為開發者提供了5種語言的示例代碼(包括C++、php、Java、Python和C#版本) 點擊下載 http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip
閱讀須知
1. EncodingAESKey長度固定為43個字符,從a-z,A-Z,0-9共62個字符中選取,公眾帳號可以在公眾平台的開發者中心的服務器配置修改;
2. AES密鑰:AESKey=Base64_Decode(EncodingAESKey + “=”),EncodingAESKey尾部填充一個字符的“=”, 用Base64_Decode生成32個字節的AESKey;
3. AES采用CBC模式,秘鑰長度為32個字節,數據采用PKCS#7填充;PKCS#7:K為秘鑰字節數(采用32),buf為待加密的內容,N為其字節數。Buf需要被填充為K的整數倍。在buf的尾部填充(K-N%K)個字節,每個字節的內容是(K- N%K);
尾部填充 | |
---|---|
01 | if ( N%K==(K-1)) |
0202 | if ( N%K==(K-2)) |
030303 | if ( N%K==(K-3)) |
... | ... |
KK....KK (K個字節) | if ( N%K==0) |
具體詳見:http://tools.ietf.org/html/rfc2315
5. 出於安全考慮,公眾平台網站提供了修改EncodingAESKey的功能(在EncodingAESKey可能泄漏時進行修改),所以建議公眾賬號保存當前的和上一次的EncodingAESKey,若當前EncodingAESKey生成的AESKey解密失敗,則嘗試用上一次的AESKey的解密。回包時,用哪個AESKey解密成功,則用此AESKey加密對應的回包;
6. 兼容模式消息體同時存在明文和密文,消息體會增至以前的3倍左右,開發者注意檢查系統,防止因消息變長和URL參數增加而出現接收錯誤;
7. 微信團隊提供了多種語言的示例代碼(包括php、Java、C++、Python、C#),請開發者盡量使用示例代碼。(http://mp.weixin.qq.com/wiki/downloads/SampleCode.zip )
下面以普通文本消息為例,詳細說明公眾平台對消息體加解密的方法和流程,其它普通消息和事件消息的加解密可以此類推。
公眾賬號接收用戶消息
消息體加密
現有消息為明文,格式如下:
msg = <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
兼容模式期間同時保留明文和密文,消息格式如下:
new_msg= <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> <Encrypt><![CDATA[msg_encrypt]]</Encrypt> </xml>
安全模式下,消息體只有密文,格式如下:
new_msg= <xml> <ToUserName><![CDATA[toUser]]</ToUserName> <Encrypt><![CDATA[msg_encrypt]]</Encrypt> </xml>
其中,msg_encrypt = Base64_Encode( AES_Encrypt[ random(16B) + msg_len(4B) + msg + $AppId] )
AES加密的buf由16個字節的隨機字符串、4個字節的msg_len(網絡字節序)、msg和$AppId組成,其中msg_len為msg的長度,$AppId為公眾帳號的AppId
AESKey =Base64_Decode(EncodingAESKey + “=”),32個字節
url上增加參數encrypt_type,encrypt_type的值為raw時表示為不加密,encrypt_type的值為aes時,表示aes加密(暫時只有raw和aes兩種值),無encrypt_type參數同樣表示不加密
消息體簽名
為了驗證消息體的合法性,公眾平台新增消息體簽名,開發者可用以驗證消息體的真實性,並對驗證通過的消息體進行解密
在url上增加參數:msg_signature
msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))
參數 | 說明 |
---|---|
Token | 公眾平台上,開發者設置的Token |
timestamp | URL上原有參數,時間戳 |
nonce | URL上原有參數,隨機數 |
msg_encrypt | 前文描述密文消息體 |
消息體驗證和解密
開發者先驗證消息體簽名的正確性,驗證通過后,再對消息體進行解密。
驗證方式
1. 開發者計算簽名,dev_msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))
2. 比較dev_msg_signature和URL上帶的msg_signature是否相等,相等則表示驗證通過
解密方式如下:
1. aes_msg=Base64_Decode(msg_encrypt)
2. rand_msg=AES_Decrypt(aes_msg)
3. 驗證尾部$AppId是否是自己的AppId,相同則表示消息沒有被篡改,這里進一步加強了消息簽名驗證
4. 去掉rand_msg頭部的16個隨機字節,4個字節的msg_len,和尾部的$AppId即為最終的xml消息體
公眾賬號向用戶回復消息
如果url上無encrypt_type或者其值為raw,則回復明文,否則按照上述的加密算法加密回復密文。兼容模式期間公眾賬號回復明文或密文均可(不要兩種類型都回)
回復消息體的簽名與加密
現有消息格式:
msg= <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> </xml>
加密后消息格式:
new_msg= <xml> <Encrypt><![CDATA[msg_encrypt]]></Encrypt> <MsgSignature><![CDATA[msg_signature]]></MsgSignature> <TimeStamp>timestamp</TimeStamp> <Nonce><![CDATA[nonce]]></Nonce> </xml>
其中,msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) + msg + $AppId])
random(16B)為16字節的隨機字符串;msg_len為msg長度,占4個字節(網絡字節序),$AppId為公眾賬號的AppId
AESKey =Base64_Decode(EncodingAESKey + “=”),32個字節
msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))
timestamp、nonce回填請求中的值或者重新生成均可
消息加解密功能開發者FAQ
Q 為什么要上線消息加密功能?
A 為了更好的保護用戶和公眾賬號的信息安全。
Q 接入消息加解密功能復雜嗎?
A 開發者接入消息加解密功能並不復雜,微信團隊提供了5種語言的示例代碼(包括C++、php、Python、Java和C#),對於使用這個5種語言的開發者,只需根據《消息加解密接入指引》,參考示例代碼,調用微信公眾平台提供的函數即可;而對於其他語言的開發者,需根據《消息加解密詳細技術方案》編寫相關代碼。
Q 消息加密功能將帶來哪些重要變化?
A 有如下幾個方面:
- 選擇明文模式時,收發消息的方式和原先相同,但安全系數較低,微信團隊推薦開發者在兼容模式下開發調試,並升級到安全模式;
- 選擇兼容模式時,消息包同時包括明文和密文,消息包的長度會相應增加到原來的3倍左右,開發者需檢查系統,做好預留,防止因消息變長而接收出錯;
- 兼容模式和安全模式下,公眾平台服務器向公眾賬號服務器配置地址URL推送消息時,將會增加兩個參數;
- 安全模式下,內容為純密文,請提前做好接收消息的解密工作和回復消息的加密工作。
Q 什么是EncodingAESKey?
A 微信公眾平台采用AES對稱加密算法對推送給公眾帳號的消息體對行加密,EncodingAESKey則是加密所用的秘鑰。公眾帳號用此秘鑰對收到的密文消息體進行解密,回復消息體也用此秘鑰加密。
Q 開發者如何判斷消息是否被加密?什么情況下需要對回包進行加密?
A 請開發者根據URL參數來判斷:url上無encrypt_type參數或者其值為raw,表示消息體僅含有明文,公眾賬號回復明文。encrypt_type為aes則表示消息體含有密文,公眾賬號回復密文(兼容模式期間回復明文或密文均可)。
Q 公眾賬號開發者上線加解密版本后,還需要保留明文解包和回包邏輯嗎?
A 暫時先保留之前的邏輯,根據參數判斷,也做成兼容模式比較好。
Q 常見錯誤舉例
A 常見錯誤原因如:
- xml格式不對:如<TimeStamp>寫成了<Timestamp > (s小寫了且p和>中間有空格)
- 公眾平台網站提供了修改EncodingAESKey的功能,公眾賬號需要保存當前的和上一次的EncodingAESKey,若當前的EncodingAESKey解密失敗,則嘗試用上一次的EncodingAESKey解密。回包時,用哪個Key解密成功,則用此Key加密對應的回包
- 調用DecryptMsg解密時,傳入的是url上的msg_signature,而不是signature
- Java要求jdk 1.6及1.6以上
- 異常java.security.InvalidKeyException:illegal Key Size的解決方案:在官方網站下載JCE無限制權限策略文件(JDK7的下載地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html 下載后解壓,可以看到local_policy.jar和US_export_policy.jar以及readme.txt,如果安裝了JRE,將兩個jar文件放到%JRE_HOME%\lib\security目錄下覆蓋原來的文件;如果安裝了JDK,將兩個jar文件放到%JDK_HOME%\jre\lib\security目錄下覆蓋原來文件