微信小程序推送消息加解密之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 })
第三步:接收消息和事件
此时可微信完成消息交互,但仅限于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
如何使用:
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实现