本文主要記錄項目中遇到的坑,其他可參考微信文檔詳情戳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 })
此時可微信完成消息交互,但僅限於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
如何使用:
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`)
以上就是微信小程序推送消息加解密node實現