使用 TypeScript 开发 Node.js 的微信开放平台/企业微信/钉钉开放平台消息 AES 加密解密库并且发布

首先附上链接:https://github.com/ecfexorg/w…

由于工作原因,先后进行过微信开放平台,企业微信,阿里钉钉的第三方开发。在这个过程中都会有解密服务器推送来的消息的需求,而且经过这几次开发后,发现微信开放平台/企业微信/阿里钉钉的加密解密算法都是使用的 AES256,同时加密的消息体结构也是一样的。

同时由于找到的第三方包的代码要么提供的功能太多,要么用了过时的 api,对于强迫症的我来说很难忍受,所以自己造了一个轮子来解决这三方的消息加密解密的需求。

使用的是 TypeScript 编写的,编译生成声明文件,用 vscode 开发能有良好的代码提示(不过这么简单的库貌似也没啥需求…)

下面说下用 TypeScript 开发一个 npm 项目并且发布到 npm 的构建和开发流程:

一、首先在 github 上创建一个项目,项目名称最好和想要发布的 npm 包名一致,创建时可以选择是否生成 README 和证书,这里我就选择了 MIT 证书和默认的 README。

二、然后在本地使用git clone命令将项目克隆下来,然后cd wx-ding-aes,再执行npm init,因为目录中包含了.git文件夹,所以 npm 初始化时可以自动填入 github 地址,文档地址等 package 属性。

我们给生成的 package.json 文件的scripts增加一条"build": "tsc",于是呆会儿执行npm run build就能编译我们的代码了。

同时为了让别人使用我们的库时也能有良好的代码提示,所以我们编译时生成声明文件在types文件夹下,发布时要连声明文件一起提交,同时还要在 package.json 中增加一个属性"types": "types/index.d.ts",意思是告诉别人这个库是自带声明文件的,并且声明文件的入口是在types目录下的index.d.ts里,当然如果你的"main"的值不是index.js,那么"types"也要改变,让它能和"main"对上号。

三、初始化完成后,执行npm i typescript @types/node -D安装 TypeScript 和 Node.js 标准库的声明文件。然后在touch tsconfig.json创建 TypeScript 的配置文件,在 vscode 中编写 TypeScript 的配置文件会有属性名和属性值提示。

{
  "compilerOptions": {
    "target": "es2017",
    "outDir": "dist",
    "module": "commonjs",
    "declaration": true,
    "declarationDir": "types"
  },
  "include": [
    "src"
  ]
}

这里我的配置很简单,"target": "es2017"表示编译目标的 JS 版本是 es2017;"outDir": "dist"表示编译后的 JS 文件放在dist文件夹中;"module": "commonjs"表示编译后的 JS 还是使用 commonjs 的模块系统;"declaration": true"declarationDir": "types"表示编译时会自动生成声明文件,并且声明文件放在types目录下。"include": [ "src" ] 表示 src 目录里面的文件会被编译。

注:Node.js 从 8.5 版本开始已经开始支持 es 模块系统,不过写这篇文章时还处于实验阶段,需要加上--experimental-modules参数才能使用

四、准备工作做完,然后可以开始写代码了,我把所有的代码都放在src目录下,写完后npm run build就能看到自动生成的dist文件夹里装着编译后的 JS 文件,而types文件夹则装着自动生成的声明文件。

关于微信的加密解密逻辑,其使用的是标准的 aes-256 加密算法的 cbc 模式,算法是可以实现的,不过 node.js 的标准库提供的crypto模块已经有了,所以我们就不需要重复造轮子(有人看到这个名词肯定会马上跑去 npmjs.org 搜 aes256,但是我觉得一个项目的依赖应该越少越好,毕竟别人写的东西质量可维护性都不可控)。aes-256 加密时,被加密的内容长度应该是 32 字节的倍数,如果不是 32 的倍数则需要进行补全。补全有很多种方式,最简单的是用 0x00 补全,但是微信要求用 pkcs7 进行补全,所以这里我们也用这种方式(有人看到肯定又会跑去 npmjs.org 搜 pkcs7 了…)。

简单几句话就能概括这种补全方式:

假设被加密的内容长度为 x

  1. 如果 x 不是 32 的倍数,可以得到比 x 大的最小的 32 的整数是 y,加密时在被加密内容后面加上 y – x 个 y – x
  2. 如果 x 是 32 的倍数,那么在被加密内容后面加上 32 个 32

两个例子:

  1. 被加密的 buffer 长度为 53 ,比 53 大的、最小的 32 的倍数的证书应该是 64 ,64 – 53 = 9,那么这个 buffer 后面就加上 9 个 9 使他的长度变成 64 ;
  2. 被加密的 buffer 长度为 64 ,那么直接加上 32 个 32 ,使他的长度变成 96 。

这样做的目的是方便解密后截取原来的数据,解密后,只要看看最后一个数是多少(假设是 x ),那么就把最后的 x 个 byte 截掉就能得到前面被补全之前的数据了。上面的两个例子中,第一个只要看到最后一个数是 9 ,那就把最后 9 个 byte 去掉,前面的就是正确的内容,第二个例子之所以补 32 个 32 ,也是因为看到最后一个数是 32 ,那就把后面的 32 个 byte 截掉即可。假如因为已经是 32 的倍数就不补全,那么就不知道是补全了的还是没补全过的了。

代码就不一一解释,因为原理就在上面了。

五、写完后npm run build就能编译了,如果想发布到 npmjs.org ,首先并不是整个项目里的所有文件都要被发布的,发布到 npm 的话应该在package.json"files"属性里选择哪些是想上传的,比如这个项目我之上传生成的 JS 和声明文件以及证书,因为源码是没必要上传的。

如果没有 npm 帐号的话,那么就要先注册一个 npm 的帐号,然后在终端里执行npm login登陆你的帐号。然后运行npm publish即可发布,如果你担心有时忘了编译就执行了publish,可以在"scripts"里面增加"prepublish": "npm run build",表示每次发布之前都会先自动执行编译脚本。更多的钩子命令可以在 npmjs.org 的官方文档查看。

六、
目前 npm 发布的包是无法使用npm unpublish <package name>来进行下架了,只能通过npm deprecate来标记过时,所以建议大家除非确定自己有能力和精力去维护一个包,否则不要轻易发布一些乱七八糟的包,更不要去占一些自己没有能力维护的好的包名。如果实在想unpublish,可以邮件联系 support@npmjs.com 说明原因,他们会根据描述来进行处理。根据我的经验,由于时差,一般白天发邮件他们会深夜回复,然后你第二天会收到回复。

希望对大家有帮助 :P

    原文作者:acookiebear
    原文地址: https://segmentfault.com/a/1190000011752560
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞