微信小程序推送消息加解密之node實現

本文主要記錄項目中遇到的坑,其他可參考微信文檔詳情戳herephp

歡迎大神評論區批評指正html

1、填寫服務器配置(須要校驗 URL 纔可提交成功)java

URL:開發者實現HTTP/HTTPS接口,用於微信返回信息node

Token & EncodingAESKey:代碼中會用,用於校驗是不是微信返回給開發者的信息python

消息加密方式:選擇明文模式,上面的URL必須是HTTPSc++

注意:當點擊提交按鈕時,微信會請求上面的URL進行校驗,此時須要走第二步git

2、驗證消息的確來自微信服務器算法

代碼實現:小程序

 1 function(token, timestamp, nonce, sig) {
 2         try {
 3             let sortList = [token, timestamp, nonce]
 4             sortList.sort() 
 5             let sha = crypto.createHash('sha1');  // 加密
 6             sha.update(sortList.join(""))
 7             return sha.digest('hex') == sig
 8         } catch (e) {
 9             console.log(e, 'getSHA1 error')
10         }
11         return false
12     }
13 
14 router.get('/api/wx_media_check', async(ctx) => {
15     let ret = wxVerify.checkSig(config.wxMsgCheck.token, ctx.query.timestamp, ctx.query.nonce, ctx.query.signature)
16     if (ret) {
17         ctx.body = ctx.query.echostr
18     } else {
19         console.error('wx check signature error')
20         ctx.body = ''
21     }
22 })
View Code

第三步:接收消息和事件

此時可微信完成消息交互,但僅限於HTTPS下的明文模式。若要對與微信的傳輸消息加密須要實現加解密,微信文檔戳herec#

文檔中只給出了c++, php, java, python, c# 5種語言的示例代碼

如下爲node實現加解密工具:

  1 const crypto = require('crypto')
  2 
  3 const ALGORITHM = 'aes-256-cbc'     // 使用的加密算法
  4 const MSG_LENGTH_SIZE = 4           // 存放消息體尺寸的空間大小。單位:字節
  5 const RANDOM_BYTES_SIZE = 16        // 隨機數據的大小。單位:字節
  6 const BLOCK_SIZE = 32               // 分塊尺寸。單位:字節
  7 
  8 let data = {
  9     appId: '',                      // 微信公衆號 APPID
 10     token: '',                      // 消息校驗 token
 11     key: '',                        // 加密密鑰
 12     iv: ''                          // 初始化向量
 13 }
 14 
 15 const Encrypt = function (params) {
 16 
 17     let { appId, encodingAESKey, token } = params
 18     let key = Buffer.from(encodingAESKey + '=', 'base64')       // 解碼密鑰
 19     let iv = key.slice(0, 16)                                   // 初始化向量爲密鑰的前16字節
 20 
 21     Object.assign(data, { appId, token, key, iv })
 22 }
 23 
 24 Encrypt.prototype = {
 25     /**
 26      * 加密消息
 27      * @param {string} msg 待加密的消息體
 28      */
 29     encode(msg) {
 30         let { appId, key, iv } = data
 31         let randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE)                     // 生成指定大小的隨機數據
 32         
 33         let msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE)                               // 申請指定大小的空間,存放消息體的大小
 34         let offset = 0                                                              // 寫入的偏移值
 35         msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset)                     // 按大端序(網絡字節序)寫入消息體的大小
 36         
 37         let msgBuf = Buffer.from(msg)                                               // 將消息體轉成 buffer
 38         let appIdBuf = Buffer.from(appId)                                           // 將 APPID 轉成 buffer
 39 
 40         let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf])    // 將16字節的隨機數據、4字節的消息體大小、若干字節的消息體、若干字節的APPID拼接起來
 41 
 42         let cipher = crypto.createCipheriv(ALGORITHM, key, iv)                      // 建立加密器實例
 43         cipher.setAutoPadding(false)                                                // 禁用默認的數據填充方式
 44         totalBuf = this.PKCS7Encode(totalBuf)                                       // 使用自定義的數據填充方式
 45         let encryptdBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()])  // 加密後的數據
 46 
 47         return encryptdBuf.toString('base64')                                       // 返回加密數據的 base64 編碼結果
 48     },
 49 
 50     /**
 51      * 解密消息
 52      * @param {string} encryptdMsg 待解密的消息體
 53      */
 54     decode(encryptdMsg) {
 55         let { key, iv } = data
 56         let encryptedMsgBuf = Buffer.from(encryptdMsg, 'base64')                                // 將 base64 編碼的數據轉成 buffer
 57 
 58         let decipher = crypto.createDecipheriv(ALGORITHM, key, iv)                              // 建立解密器實例
 59         decipher.setAutoPadding(false)                                                          // 禁用默認的數據填充方式
 60         let decryptdBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()])   // 解密後的數據
 61 
 62         decryptdBuf = this.PKCS7Decode(decryptdBuf)                                             // 去除填充的數據
 63 
 64         let msgSize = decryptdBuf.readUInt32BE(RANDOM_BYTES_SIZE)                               // 根據指定偏移值,從 buffer 中讀取消息體的大小,單位:字節
 65         let msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE                                // 消息體的起始位置
 66         let msgBufEndPos = msgBufStartPos + msgSize                                             // 消息體的結束位置
 67 
 68         let msgBuf = decryptdBuf.slice(msgBufStartPos, msgBufEndPos)                            // 從 buffer 中提取消息體
 69 
 70         return msgBuf.toString()                                                                // 將消息體轉成字符串,並返回數據
 71     },
 72 
 73     /**
 74      * 生成簽名
 75      * @param {Object} params 待簽名的參數
 76      */
 77     genSign(params) {
 78         let { token } = data
 79         let { timestamp, nonce, encrypt } = params
 80 
 81         let rawStr = [token, timestamp, nonce, encrypt].sort().join('')                         // 原始字符串
 82         let signature = crypto.createHash('sha1').update(rawStr).digest('hex')                  // 計算簽名
 83 
 84         return signature
 85     },
 86 
 87     /**
 88      * 按 PKCS#7 的方式從填充過的數據中提取原數據
 89      * @param {Buffer} buf 待處理的數據
 90      */
 91     PKCS7Decode(buf) {
 92         let padSize = buf[buf.length - 1]                       // 最後1字節記錄着填充的數據大小
 93         return buf.slice(0, buf.length - padSize)               // 提取原數據
 94     },
 95 
 96     /**
 97      * 按 PKCS#7 的方式填充數據結尾
 98      * @param {Buffer} buf 待填充的數據
 99      */
100     PKCS7Encode(buf) {
101         let padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE)    // 計算填充的大小。
102         let fillByte = padSize                                  // 填充的字節數據爲填充的大小
103         let padBuf = Buffer.alloc(padSize, fillByte)            // 分配指定大小的空間,並填充數據
104         return Buffer.concat([buf, padBuf])                     // 拼接原數據和填充的數據
105     }
106 }
107 
108 module.exports = Encrypt
View Code

如何使用:

 1 const WechatEncrypt = require('./gitwechat')
 2 
 3 const wechatEncrypt = new WechatEncrypt({
 4     appId: 'wx013591feaf25uoip',  // 開發者小程序APPID
 5     encodingAESKey: 'abcdefgabcdefgabcdefgabcdefgabcdefgabcdefg0',  // 開發者在第一步填寫服務器配置的encodingAESKey
 6     token: 'test token'  // 開發者在第一步填寫服務器配置的token
 7 })
 8 
 9 // 報文主體中 Encrypt 字段的值  如下參數是微信返回給開發者的參數
10 let encrypt = 'elJAUQEY0yKnbLbmXYdacAoDEmJlzdMeB3ryWEtNOQnJ2n1h9Y0ocSYYsW8YsrVrWhJrZe4gKKrzMs1JBCHFNHlFYCMBigDMU41WGxjwulsLjglXd+Cr7Mq/RV7TUwkkqX9+y0KmIIqAl+qYJUnuYvaug5bBMcikP9kDj3OzQ41Oppt0hzNGq7tw6RFplSW75ItMVY6Vi0d+NJTLuvIWwQqDIytcVJnNQFHOTRmm9sUVVm0kNiQp7sQljoif+j/JjMkB1fQXtrwUkLup0ql4vGZ8/126qWFR8p8tmzbDm4U/tdgLYLnEv7XFMT6cmYprmEz3cyN2yWuRfKcCBOgKyUfEt+NYwnE+1l5QK2nbOkMqorqmvc66zo0VYVj4o8nV+laMy3Celz3rDUAJMKXk/FN8ZjOsyn7sDJlo8iAhHtg='
11 let timestamp = '1565268520' // 推送消息連接上的 timestamp 字段值
12 let nonce = '331748743'    // 推送消息連接上的 nonce 字段值
13 let msg_signature = 'f0d525f5e849b1cd8f628eff2121b4d16765b7f2' // 推送消息連接上 msg_signature 字段值
14 
15 // 校驗消息是否來自微信:取連接上的 timestamp, nonce 字段和報文主體的 Encrypt 字段的值,來生成簽名
16 // 生成的簽名和連接上的 msg_signature 字段值進行對比
17 let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt })
18 let isValid = signature === msg_signature
19 console.log(`該消息${isValid ? '有效' : '無效'}\n\n`)
20 /*
21 該消息有效
22 
23 */
24 25 
25 // 解密消息內容。取報文主體的 Encrypt 字段的值進行解密
26 let xml = wechatEncrypt.decode(encrypt)
27 console.log(`解密後的消息:\n${xml}\n\n`)
28 /*
29 解密後的消息:
30 <xml><ToUserName><![CDATA[gh_fd189404d989]]></ToUserName><FromUserName><![CDATA[o9uKB5hniJXLYJTtfjxMSSmo477k]]></FromUserName><CreateTime>1565266686</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[Hello world]]></Content><MsgId>22409229427342621</MsgId></xml>
31 */
32 
33 // 加密消息。調用 encode 方法,傳入待加密的內容,返回加密後的結果
34 let encryptedMsg = wechatEncrypt.encode(xml)
35 console.log(`加密後的結果:\n${encryptedMsg}\n\n`)
View Code

以上就是微信小程序推送消息加解密node實現

相關文章
相關標籤/搜索