使用 TypeScript 開發 Node.js 的微信開放平臺/企業微信/釘釘開放平臺訊息 AES 加密解密庫並且釋出

acookiebear發表於2019-02-16

首先附上鍊接: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 說明原因,他們會根據描述來進行處理。根據我的經驗,由於時差,一般白天發郵件他們會深夜回覆,然後你第二天會收到回覆。

希望對大家有幫助 ?

相關文章