├── .gitignore ├── README.md ├── lib └── encrypt.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wechat-encrypt 2 | 3 | 微信开放平台会话消息加解密模块。 4 | 5 | ### 示例代码 6 | 7 | ```bash 8 | npm install wechat-encrypt 9 | ``` 10 | 11 | ```javascript 12 | const WechatEncrypt = require('wechat-encrypt') 13 | 14 | const wechatEncrypt = new WechatEncrypt({ 15 | appId: 'wx013591feaf25uoip', 16 | encodingAESKey: 'abcdefgabcdefgabcdefgabcdefgabcdefgabcdefg0', 17 | token: 'test token' 18 | }) 19 | 20 | // 报文主体中 Encrypt 字段的值(注意微信推送消息体的格式为 XML) 21 | let encrypt = 'elJAUQEY0yKnbLbmXYdacAoDEmJlzdMeB3ryWEtNOQnJ2n1h9Y0ocSYYsW8YsrVrWhJrZe4gKKrzMs1JBCHFNHlFYCMBigDMU41WGxjwulsLjglXd+Cr7Mq/RV7TUwkkqX9+y0KmIIqAl+qYJUnuYvaug5bBMcikP9kDj3OzQ41Oppt0hzNGq7tw6RFplSW75ItMVY6Vi0d+NJTLuvIWwQqDIytcVJnNQFHOTRmm9sUVVm0kNiQp7sQljoif+j/JjMkB1fQXtrwUkLup0ql4vGZ8/126qWFR8p8tmzbDm4U/tdgLYLnEv7XFMT6cmYprmEz3cyN2yWuRfKcCBOgKyUfEt+NYwnE+1l5QK2nbOkMqorqmvc66zo0VYVj4o8nV+laMy3Celz3rDUAJMKXk/FN8ZjOsyn7sDJlo8iAhHtg=' 22 | let timestamp = '1565268520' // 推送消息链接上的 timestamp 字段值 23 | let nonce = '331748743' // 推送消息链接上的 nonce 字段值 24 | let msg_signature = 'f0d525f5e849b1cd8f628eff2121b4d16765b7f2' // 推送消息链接上 msg_signature 字段值 25 | 26 | // 校验消息是否来自微信:取链接上的 timestamp, nonce 字段和报文主体的 Encrypt 字段的值,来生成签名 27 | // 生成的签名和链接上的 msg_signature 字段值进行对比 28 | let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt }) 29 | let isValid = signature === msg_signature 30 | console.log(`该消息${isValid ? '有效' : '无效'}\n\n`) 31 | /* 32 | 该消息有效 33 | 34 | */ 35 | 36 | // 解密消息内容。取报文主体的 Encrypt 字段的值进行解密 37 | let xml = wechatEncrypt.decode(encrypt) 38 | console.log(`解密后的消息:\n${xml}\n\n`) 39 | /* 40 | 解密后的消息: 41 | 156526668622409229427342621 42 | */ 43 | 44 | 45 | // 加密消息。调用 encode 方法,传入待加密的内容,返回加密后的结果 46 | let encryptedMsg = wechatEncrypt.encode(xml) 47 | console.log(`加密后的结果:\n${encryptedMsg}\n\n`) 48 | /* 49 | 加密后的结果: 50 | uF/fQ1LOkmHC4defoc2+h1LxRFXh2dGu4CS71Nm7I2BrWglcchikzFJw1RN9ZsylVyow1kGBj7p9Mrg0m6VGdrSKZ/aCg04Yu9lCCY7YPukf7VpBR+iK8JNiproQTdnXWREar2UPWM06aGPmQTfVcjEN1K5oMA0tOxRFt2jDtjhwCptXw7qPALCT8fJILkjL7z8e//dMCtrrxeh0NENf3oM1AqZq7ZJ/iWHfCPp+hcxNJrNZlzLgKlIuFxb8QwppvA8KyOItM+RZkr286e1hPJqnCpelXrl9MzigrnGH+BjegkQQNrHBco093vrElrOJxYnlJwHOtr/kN54nngFal/Gn1+PRCrvVPxRKE2e/pwTbCMtUbVB+W3FKTnbGDfEvzBJzrPKYmT2Woio3hTjsYEb8Qk/fMc8A8myalD3CD8pjTAY0/dTmo3Iq4jwrhwQ9HnvGxliwZ25lWRxplwQDf4aB6kngfC4tZnrNuDKewUyWr7RKrpGjxV9OtzzbZBaa 51 | */ 52 | ``` 53 | 54 | ### WechatEncrypt(params) 55 | 56 | **构造函数** 57 | 58 | ```javascript 59 | let wechatEncrypt = new WechatEncrypt({ appId, token, encodingAESKey }) 60 | ``` 61 | 62 | ## 实例方法: 63 | 64 | ### encode(msg) 65 | 66 | **加密消息** 67 | 68 | ```javascript 69 | let encryptedMsg = wechatEncrypt.encode(rawMsg) 70 | ``` 71 | 72 | ### decode(msg) 73 | 74 | **解密消息** 75 | 76 | ```javascript 77 | let decryptedMsg = wechatEncrypt.decode(encryptedMsg) 78 | ``` 79 | 80 | ### genSign(params) 81 | 82 | **生成签名 signature。用于校验消息是否来自微信。回复消息时也需要生成签名。** 83 | 84 | ```javascript 85 | let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt }) 86 | ``` 87 | 88 | -------------------------------------------------------------------------------- /lib/encrypt.js: -------------------------------------------------------------------------------- 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 | const Encrypt = function (params) { 9 | 10 | let { appId, encodingAESKey, token } = params 11 | let key = Buffer.from(encodingAESKey + '=', 'base64') // 解码密钥 12 | let iv = key.slice(0, 16) // 初始化向量为密钥的前16字节 13 | 14 | Object.assign(this, { appId, token, key, iv }) 15 | } 16 | 17 | Encrypt.prototype = { 18 | /** 19 | * 加密消息 20 | * @param {string} msg 待加密的消息体 21 | */ 22 | encode(msg) { 23 | let { appId, key, iv } = this 24 | let randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE) // 生成指定大小的随机数据 25 | 26 | let msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE) // 申请指定大小的空间,存放消息体的大小 27 | let offset = 0 // 写入的偏移值 28 | msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset) // 按大端序(网络字节序)写入消息体的大小 29 | 30 | let msgBuf = Buffer.from(msg) // 将消息体转成 buffer 31 | let appIdBuf = Buffer.from(appId) // 将 APPID 转成 buffer 32 | 33 | let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf]) // 将16字节的随机数据、4字节的消息体大小、若干字节的消息体、若干字节的APPID拼接起来 34 | 35 | let cipher = crypto.createCipheriv(ALGORITHM, key, iv) // 创建加密器实例 36 | cipher.setAutoPadding(false) // 禁用默认的数据填充方式 37 | totalBuf = this.PKCS7Encode(totalBuf) // 使用自定义的数据填充方式 38 | let encryptedBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()]) // 加密后的数据 39 | 40 | return encryptedBuf.toString('base64') // 返回加密数据的 base64 编码结果 41 | }, 42 | 43 | /** 44 | * 解密消息 45 | * @param {string} encryptedMsg 待解密的消息体 46 | */ 47 | decode(encryptedMsg) { 48 | let { key, iv } = this 49 | let encryptedMsgBuf = Buffer.from(encryptedMsg, 'base64') // 将 base64 编码的数据转成 buffer 50 | 51 | let decipher = crypto.createDecipheriv(ALGORITHM, key, iv) // 创建解密器实例 52 | decipher.setAutoPadding(false) // 禁用默认的数据填充方式 53 | let decryptedBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()]) // 解密后的数据 54 | 55 | decryptedBuf = this.PKCS7Decode(decryptedBuf) // 去除填充的数据 56 | 57 | let msgSize = decryptedBuf.readUInt32BE(RANDOM_BYTES_SIZE) // 根据指定偏移值,从 buffer 中读取消息体的大小,单位:字节 58 | let msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE // 消息体的起始位置 59 | let msgBufEndPos = msgBufStartPos + msgSize // 消息体的结束位置 60 | 61 | let msgBuf = decryptedBuf.slice(msgBufStartPos, msgBufEndPos) // 从 buffer 中提取消息体 62 | 63 | return msgBuf.toString() // 将消息体转成字符串,并返回数据 64 | }, 65 | 66 | /** 67 | * 生成签名 68 | * @param {Object} params 待签名的参数 69 | */ 70 | genSign(params) { 71 | let { token } = this 72 | let { timestamp, nonce, encrypt } = params 73 | 74 | let rawStr = [token, timestamp, nonce, encrypt].sort().join('') // 原始字符串 75 | let signature = crypto.createHash('sha1').update(rawStr).digest('hex') // 计算签名 76 | 77 | return signature 78 | }, 79 | 80 | /** 81 | * 按 PKCS#7 的方式从填充过的数据中提取原数据 82 | * @param {Buffer} buf 待处理的数据 83 | */ 84 | PKCS7Decode(buf) { 85 | let padSize = buf[buf.length - 1] // 最后1字节记录着填充的数据大小 86 | return buf.slice(0, buf.length - padSize) // 提取原数据 87 | }, 88 | 89 | /** 90 | * 按 PKCS#7 的方式填充数据结尾 91 | * @param {Buffer} buf 待填充的数据 92 | */ 93 | PKCS7Encode(buf) { 94 | let padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE) // 计算填充的大小。 95 | let fillByte = padSize // 填充的字节数据为填充的大小 96 | let padBuf = Buffer.alloc(padSize, fillByte) // 分配指定大小的空间,并填充数据 97 | return Buffer.concat([buf, padBuf]) // 拼接原数据和填充的数据 98 | } 99 | } 100 | 101 | module.exports = Encrypt 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat-encrypt", 3 | "version": "1.1.1", 4 | "description": "微信开放平台会话消息加解密工具", 5 | "main": "lib/encrypt.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/pengng/wechat-encrypt.git" 9 | }, 10 | "keywords": [ 11 | "加密", 12 | "解密", 13 | "消息", 14 | "开放平台", 15 | "微信公众号", 16 | "wechat" 17 | ], 18 | "author": "yupeng.kuo ", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/pengng/wechat-encrypt/issues" 22 | }, 23 | "homepage": "https://github.com/pengng/wechat-encrypt#readme" 24 | } 25 | --------------------------------------------------------------------------------