UNPKG

@romejiang/koa-wechat-public

Version:

一款基于koa的微信公众号开发中间件

102 lines (101 loc) 4.66 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = __importDefault(require("crypto")); const xml2js_1 = __importDefault(require("xml2js")); const buildXML = new xml2js_1.default.Builder({ rootName: 'xml', cdata: true, headless: true, renderOpts: { indent: ' ', pretty: true } }); class CryptoGraphy { constructor(config) { this.aesModel = 'aes-256-cbc'; if (config) { this.token = config.token; this.appId = config.appId; this.encodingAESKey = config.encodingAESKey ? Buffer.from(`${config.encodingAESKey}=`, 'base64') : undefined; this.iv = this.encodingAESKey ? this.encodingAESKey.slice(0, 16) : undefined; } } KCS7Encoder(textLength) { // eslint-disable-next-line camelcase const block_size = 32; // eslint-disable-next-line camelcase let amount_to_pad = block_size - (textLength % block_size); // eslint-disable-next-line camelcase if (amount_to_pad === 0) { // eslint-disable-next-line camelcase amount_to_pad = block_size; } const pad = String.fromCharCode(amount_to_pad); const s = []; // eslint-disable-next-line camelcase for (let i = 0; i < amount_to_pad; i++) s.push(pad); return s.join(''); } encryptMsg(xmlMsg) { const random = crypto_1.default.randomBytes(8).toString('hex'); const text = Buffer.from(xmlMsg); const buf = Buffer.alloc(4); buf.writeUInt32BE(text.length); const pack = this.KCS7Encoder(20 + text.length + this.appId.length); const content = random + buf.toString('binary') + text.toString('binary') + this.appId + pack; const cipheriv = crypto_1.default.createCipheriv(this.aesModel, this.encodingAESKey, this.iv); cipheriv.setAutoPadding(false); const encryptedMsg = Buffer.concat([cipheriv.update(content, 'binary'), cipheriv.final()]).toString('base64'); const msgSignature = this.getMsgSignature(encryptedMsg); return buildXML.buildObject({ Encrypt: encryptedMsg, MsgSignature: msgSignature, TimeStamp: this.timestamp, Nonce: this.nonce }); } init(config) { this.token = config.token ? config.token : this.token; this.appId = config.appId ? config.appId : this.appId; this.encodingAESKey = config.encodingAESKey ? Buffer.from(`${config.encodingAESKey}=`, 'base64') : this.encodingAESKey; this.iv = config.encodingAESKey ? this.encodingAESKey.slice(0, 16) : this.iv; this.msgSignature = config.msgSignature; this.timestamp = config.timestamp; this.nonce = config.nonce; return this; } decryptMsg(encryptMsg) { const tempSignature = this.getMsgSignature(encryptMsg); if (this.msgSignature !== tempSignature) { throw new Error('msgSignature is not invalid'); } const deCipheriv = crypto_1.default.createDecipheriv(this.aesModel, this.encodingAESKey, this.iv); deCipheriv.setAutoPadding(false); let deEncryptedMsg = Buffer.concat([deCipheriv.update(encryptMsg, 'base64'), deCipheriv.final()]).toString('utf8'); const pad = deEncryptedMsg.charCodeAt(deEncryptedMsg.length - 1); deEncryptedMsg = deEncryptedMsg.slice(20, -pad).replace(/<\/xml>.*/, '</xml>'); return this.parseXmlToJSON(deEncryptedMsg); } parseXmlToJSON(xml) { if (!xml || typeof xml !== 'string') return {}; const re = {}; xml = xml.replace(/^<xml>|<\/xml>$/g, ''); const ms = xml.match(/<([a-z0-9]+)>([\s\S]*?)<\/\1>/ig); if (ms && ms.length > 0) { ms.forEach(t => { const ms = t.match(/<([a-z0-9]+)>([\s\S]*?)<\/\1>/i); const tagName = ms[1]; let cdata = ms[2] || ''; // eslint-disable-next-line no-useless-escape cdata = cdata.replace(/^\s*<\!\[CDATA\[\s*|\s*\]\]>\s*$/g, ''); re[tagName] = cdata; }); } return re; } getMsgSignature(encryptMsg) { const tempStr = [this.token, this.timestamp, this.nonce, encryptMsg].sort().join(''); const hashCode = crypto_1.default.createHash('sha1'); const resultCode = hashCode.update(tempStr, 'utf8').digest('hex'); return resultCode; } } exports.default = CryptoGraphy;