UNPKG

vtils

Version:

一个面向业务的 JavaScript/TypeScript 实用程序库。

130 lines (123 loc) 3.78 kB
import crypto from 'crypto'; import { parseXml } from "./parseXml.js"; import { sha1 } from "./sha1.js"; /** * 微信公众号消息加解密。 */ export class WechatMessageCrypto { constructor(options) { this.options = void 0; this.aesKey = void 0; this.iv = void 0; this.options = options; this.aesKey = Buffer.from(`${options.encodingAESKey}=`, 'base64'); this.iv = this.aesKey.slice(0, 16); } /** * 加密原始消息。 * * @param msg 原始消息 */ encryptMsg(msg) { const randomBytes = crypto.randomBytes(16); const msgLenBuf = Buffer.alloc(4); const offset = 0; msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset); const msgBuf = Buffer.from(msg); const appIdBuf = Buffer.from(this.options.appId); let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf]); const cipher = crypto.createCipheriv('aes-256-cbc', this.aesKey, this.iv); cipher.setAutoPadding(false); totalBuf = this.PKCS7Encode(totalBuf); const encryptedBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()]); return encryptedBuf.toString('base64'); } /** * 签名。 * * @param options 选项 */ sign(options) { return sha1([this.options.token, options.timestamp, options.nonceStr, options.encryptedMsg].sort((a, b) => { a = a.toString(); b = b.toString(); return a > b ? 1 : a < b ? -1 : 0; }).join('')); } /** * 签名加密后的消息。 * * @param encryptedMsg 加密后的消息 */ signEncryptedMsg(encryptedMsg) { const timestamp = Math.round(Date.now() / 1000); const nonceStr = timestamp.toString(36); const signature = this.sign({ timestamp, nonceStr, encryptedMsg }); return { timestamp, nonceStr, signature }; } /** * 签名加密后的消息并返回封装好的 XML。 * * @param encryptedMsg 加密后的消息 */ signEncryptedMsgAsXml(encryptedMsg) { const { timestamp, nonceStr, signature } = this.signEncryptedMsg(encryptedMsg); return `<xml>` + `<Encrypt><![CDATA[${encryptedMsg}]]></Encrypt>` + `<MsgSignature><![CDATA[${signature}]]></MsgSignature>` + `<TimeStamp>${timestamp}</TimeStamp>` + `<Nonce><![CDATA[${nonceStr}]]></Nonce>` + `</xml>`; } /** * 检查签名是否正确。 * * @param signature 要验证的签名 * @param payload 载荷 */ checkSignature(signature, payload) { return this.sign(payload) === signature; } /** * 解密加密后的消息。 * * @param encryptedMsg 加密后的消息 */ decryptEncryptedMsg(encryptedMsg) { const encryptedMsgBuf = Buffer.from(encryptedMsg, 'base64'); const decipher = crypto.createDecipheriv('aes-256-cbc', this.aesKey, this.iv); decipher.setAutoPadding(false); let decryptedBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()]); decryptedBuf = this.PKCS7Decode(decryptedBuf); const msgSize = decryptedBuf.readUInt32BE(16); const msgBufStartPos = 16 + 4; const msgBufEndPos = msgBufStartPos + msgSize; const msgBuf = decryptedBuf.slice(msgBufStartPos, msgBufEndPos); return msgBuf.toString(); } /** * 解密加密后的消息并作为 XML 解码返回。 * * @param encryptedMsg 加密后的消息 */ decryptEncryptedMsgAsXml(encryptedMsg) { return parseXml(this.decryptEncryptedMsg(encryptedMsg)).xml; } PKCS7Decode(buf) { const padSize = buf[buf.length - 1]; return buf.slice(0, buf.length - padSize); } PKCS7Encode(buf) { const padSize = 32 - buf.length % 32; const fillByte = padSize; const padBuf = Buffer.alloc(padSize, fillByte); return Buffer.concat([buf, padBuf]); } }