總算通過了叠荠,在網(wǎng)上找了很多人的實(shí)現(xiàn)方法,都有這樣那樣的問題扫责。本人對加密完全是小白一個(gè)榛鼎,微信官方的加密解密技術(shù)方案看了很久才勉強(qiáng)弄明白,總結(jié)一下需要注意的地方鳖孤。
- 數(shù)據(jù)使用
PKCS#7
填充者娱,填充規(guī)則看文檔。通俗一點(diǎn)的解釋是你用來加密的buf
必須是 32 位的整數(shù)倍苏揣,如果加密前的buf
長度不是整數(shù)倍怎么辦呢黄鳍?就要給它補(bǔ)上,補(bǔ)(32 - N%32)
個(gè)(32-N%32)
平匈, 比如你長度是 30框沟,后面就要2個(gè)02,29就是補(bǔ)3個(gè) 03增炭。這里有個(gè)問題是 crypto 模塊默認(rèn)是自動填充的忍燥,而且填充的內(nèi)容規(guī)則不同,需要自己來實(shí)現(xiàn)填充規(guī)則隙姿,而且要設(shè)置setAutoPadding(false)
梅垄。下面代碼有實(shí)現(xiàn),可以參考输玷。 - 解密格式為 'aes-256-cbc' ,官方說法是‘AES采用CBC模式队丝,秘鑰長度為32個(gè)字節(jié)(256位)’靡馁,琢磨了很久。用通俗一點(diǎn)的說法是机久,
- 下面代碼中奈嘿,消息加密的方法,傳入的參數(shù)是明文回復(fù)的消息文本
<xml>...</xml>
吞加,返回的是一段字符串文本裙犹,需要自己包裝,最后回復(fù)給用戶的內(nèi)容是<xml><ToUserName><![CDATA[toUser]]</ToUserName> <Encrypt><![CDATA[ 加密方法返回的字符串文本 ]]</Encrypt> </xml>
衔憨,解密的方法則相反叶圃,傳入的是加密字符串文本,返回的是加密前的xml
消息文本践图,和官方文檔中的消息格式說明是一樣的掺冠。
'use strict'
/**
* WxCrypt 微信消息加解密的方法
* @return new WxCrypt(wxConfig)
* 返回一個(gè)單例即可,不需要重復(fù)引用
*/
const crypto = require('crypto');
const wxConfig = {
// 傳入配置信息
token: 'xxx',
appid: 'xxx',
msg_signature: 'xxx',
encodingAESKey: 'xxx'
}
class WxCrypt {
constructor(opts){
//初始化需要用到的屬性
this.token = opts.token;
this.appid = opts.appid;
this.msg_signature = opts.msg_signature; //query 傳進(jìn)來簽名
this.aesKey = new Buffer(opts.encodingAESKey + '=', 'base64');
this.IV = this.aesKey.slice(0, 16)
}
encrypt (xmlMsg) {
/*
*@params String xmlMsg 格式化后的 xml 字符串
*@return String 加密后的字符串 填入到 Encrypt 節(jié)點(diǎn)中
* 參照官方文檔 需要返回一個(gè)buf: 隨機(jī)16字節(jié) + xmlMsg.length(4字節(jié))+xmlMsg+appid码党。
* buf的字節(jié)長度需要填充到 32的整數(shù)德崭,填充長度為 32-buf.length%32, 每一個(gè)字節(jié)為 32-buf.length%32
*/
let random16 = crypto.pseudoRandomBytes(16);
let msg = new Buffer(xmlMsg);
let msgLength = new Buffer(4);
msgLength.writeUInt32BE(msg.length, 0);
let corpId = new Buffer(this.appid);
let raw_msg = Buffer.concat([random16,msgLength,msg ,corpId]);
let cipher = crypto.createCipheriv('aes-256-cbc', this.aesKey, this.IV);
cipher.setAutoPadding(false);//重要,autopadding填充的內(nèi)容無法正常解密
raw_msg = this.PKCS7Encode(raw_msg);
let cipheredMsg = Buffer.concat([cipher.update(/*encoded*/raw_msg), cipher.final()]);
return cipheredMsg.toString('base64');
}
decrypt(text) {
/*
*@params String text 需要解密的字段(Encrypt節(jié)點(diǎn)中的內(nèi)容)
* @return String msg_content 返回消息內(nèi)容(xml字符串)
*/
let plain_text;
let decipher = crypto.Decipheriv('aes-256-cbc', this.aesKey, this.IV)
// crypto.Decipheriv == crypto.createDecipheriv 兩個(gè)方法是一樣的
decipher.setAutoPadding(false);//重要
let decipheredBuff = Buffer.concat([decipher.update(text, 'base64'), decipher.final()])
decipheredBuff = this.PKCS7Decode(decipheredBuff)
let len_netOrder_corpid = decipheredBuff.slice(16)
//切割掉16個(gè)隨機(jī)字符揖盘,剩余為 (4字節(jié)的 msg_len) + msg_content(長度為 msg_len ) + msg_appId
let msg_len = len_netOrder_corpid.slice(0, 4).readUInt32BE(0)
let msg_content = len_netOrder_corpid.slice(4, msg_len+4).toString('utf-8')
// let msg_appId =len_netOrder_corpid.slice(msg_len+4).toString('utf-8')
return msg_content
}
PKCS7Decode(buff) {
/*
*去除尾部自動填充的內(nèi)容
*/
let padContent = buff[length -1]
if(padContent<1 || padContent >32){
padContent = 0
}
let padLen = padContent;//根據(jù)填充規(guī)則眉厨,填充長度 = 填充內(nèi)容,這一步賦值可以省略
return buff.slice(0, buff.length - padLen)
}
PKCS7Encode(buff) {
let blockSize = 32;
let needPadLen = 32 - buff.length % 32
if( needPadLen == 0) {
needPadLen = blockSize
}
let pad = new Buffer(needPadLen)
pad.fill(needPadLen)
let newBuff = Buffer.concat([buff, pad])
return newBuff
}
}
var wxCrypt = new WxCrypt(wxConfig);
module.exports = wxCrypt;