NodeJS开发微信记录(1)---安全模式AES加解密

总算通过了,在网上找了很多人的实现方法,都有这样那样的问题。本人对加密完全是小白一个,微信官方的加密解密技术方案看了很久才勉强弄明白,总结一下需要注意的地方。

  1. 数据使用 PKCS#7填充,填充规则看文档。通俗一点的解释是你用来加密的 buf 必须是 32 位的整数倍,如果加密前的buf长度不是整数倍怎么办呢?就要给它补上,补 (32 - N%32)(32-N%32), 比如你长度是 30,后面就要2个02,29就是补3个 03。这里有个问题是 crypto 模块默认是自动填充的,而且填充的内容规则不同,需要自己来实现填充规则,而且要设置 setAutoPadding(false)。下面代码有实现,可以参考。
  2. 解密格式为 'aes-256-cbc' ,官方说法是‘AES采用CBC模式,秘钥长度为32个字节(256位)’,琢磨了很久。用通俗一点的说法是,
  3. 下面代码中,消息加密的方法,传入的参数是明文回复的消息文本 <xml>...</xml>,返回的是一段字符串文本,需要自己包装,最后回复给用户的内容是 <xml><ToUserName><![CDATA[toUser]]</ToUserName> <Encrypt><![CDATA[ 加密方法返回的字符串文本 ]]</Encrypt> </xml>,解密的方法则相反,传入的是加密字符串文本,返回的是加密前的 xml 消息文本,和官方文档中的消息格式说明是一样的。
'use strict'
/**
 * WxCrypt 微信消息加解密的方法
 * @return new WxCrypt(wxConfig)
 * 返回一个单例即可,不需要重复引用
 */
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 传进来签名
        this.aesKey = new Buffer(opts.encodingAESKey + '=', 'base64');
        this.IV = this.aesKey.slice(0, 16)
    }
    encrypt (xmlMsg) {
        /*
         *@params String xmlMsg 格式化后的 xml 字符串
         *@return String 加密后的字符串 填入到 Encrypt 节点中
         * 参照官方文档 需要返回一个buf: 随机16字节 + xmlMsg.length(4字节)+xmlMsg+appid。
         * buf的字节长度需要填充到 32的整数,填充长度为 32-buf.length%32, 每一个字节为 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填充的内容无法正常解密
       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节点中的内容)
           * @return String msg_content 返回消息内容(xml字符串)
           */
        
          let plain_text;
          let decipher = crypto.Decipheriv('aes-256-cbc', this.aesKey, this.IV)
// crypto.Decipheriv == crypto.createDecipheriv 两个方法是一样的
           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个随机字符,剩余为 (4字节的 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) {
        /*
         *去除尾部自动填充的内容
         */
        let padContent = buff[length -1]
            if(padContent<1 || padContent >32){
                    padContent = 0
            }
        let padLen = padContent;//根据填充规则,填充长度 = 填充内容,这一步赋值可以省略
        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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • 本文主要介绍移动端的加解密算法的分类、其优缺点特性及应用,帮助读者由浅入深地了解和选择加解密算法。文中会包含算法的...
    苹果粉阅读 11,421评论 5 29
  • 这篇文章主要讲述在Mobile BI(移动商务智能)开发过程中,在网络通信、数据存储、登录验证这几个方面涉及的加密...
    雨_树阅读 2,323评论 0 6
  • 之前的项目中接触过一些加密的方法,也没有太仔细的进行记录和研究。最近在写SDK时,加密模块的占比相当之大;借此时机...
    大雄記阅读 10,938评论 20 63
  • 最近,看了一部电影,《白日梦想家》。 《白日梦想家》是由本·斯蒂勒自导自演,《当幸福来敲门》的编剧史蒂夫·...
    生活旅行家东子阅读 549评论 0 0
  • 初步描绘,等到彩色铅笔到手后,继续修改。我觉得世界应该有色彩,而不是单一的黑白。期待我的下一步吧!
    史啸雲歪阅读 355评论 0 1