├── .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 |
--------------------------------------------------------------------------------