微信小程序推送消息加解密之node实现

本文主要记录项目中遇到的坑,其余可参考微信文档详情戳here

欢迎大神评论区批评指正

一、填写服务器配置(需要校验 URL 才可提交成功)

URL:开发者实现HTTP/HTTPS接口,用于微信返回信息

Token & EncodingAESKey:代码中会用,用于校验是否是微信返回给开发者的信息

消息加密方式:选择明文模式,上面的URL必须是HTTPS

注意:当点击提交按钮时,微信会请求上面的URL进行校验,此时需要走第二步

二、验证消息的确来自微信服务器

代码实现:

 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下的明文模式。若要对与微信的传输消息加密需要实现加解密,微信文档戳here

文档中只给出了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 ? '有效' : '无效'}

`)
20 /*
21 该消息有效
22 
23 */
24 25 
25 // 解密消息内容。取报文主体的 Encrypt 字段的值进行解密
26 let xml = wechatEncrypt.decode(encrypt)
27 console.log(`解密后的消息:
${xml}

`)
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(`加密后的结果:
${encryptedMsg}

`)
View Code

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

原文地址:https://www.cnblogs.com/luoyangyang/p/11584461.html