zoukankan      html  css  js  c++  java
  • 微信小程序推送消息加解密之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实现

  • 相关阅读:
    利用子查询解决复杂sql问题
    如何用临时表代替游标进行表记录的拷贝
    SQL新函数, 排名函数 ROW_NUMBER(), RANK(), DENSE_RANK()
    SQL SERVER2005 中的错误捕捉与处理
    用户自定义函数代替游标进行循环拼接
    使用游标进行循环数据插入
    Oracle中利用存储过程建表
    SQL SERVER中强制类型转换cast和convert的区别
    SQL如何修改被计算字段引用的字段类型
    1.官方网站:http://www.mplayerhq.hu/design7/dload.html
  • 原文地址:https://www.cnblogs.com/luoyangyang/p/11584461.html
Copyright © 2011-2022 走看看