上一篇文章分享了WebAssembly概念和基本使用,經過兩個代碼示例的分析對WebAssembly有了大體的瞭解。這一篇文章分享的是基於WebAssembly的加密工具實踐,咱們就以openssl的摘要算法md5和sha1爲例,在Mac上編譯openSSL到WebAssembly。javascript
將Openssl編譯到WebAssembly整個流程是這樣的,md5.c文件–>emscripten編譯–>.wasm文件–>結合WebAssembly JS API–>瀏覽器中運行。html
//md5.c #include <emscripten.h> #include <openssl/md5.h> #include <openssl/sha.h> #include <string.h> #include <stdio.h> EMSCRIPTEN_KEEPALIVE void md5(char *str, char *result,int strlen) { MD5_CTX md5_ctx; int MD5_BYTES = 16; unsigned char md5sum[MD5_BYTES]; MD5_Init(&md5_ctx); MD5_Update(&md5_ctx, str,strlen); MD5_Final(md5sum, &md5_ctx); char temp[3] = {0}; memset(result,0, sizeof(char) * 32); for (int i = 0; i < MD5_BYTES; i++) { sprintf(temp, "%02x", md5sum[i]); strcat(result, temp); } result[32] = '\0'; } EMSCRIPTEN_KEEPALIVE void sha1(char *str, char result[],int strlen) { unsigned char digest[SHA_DIGEST_LENGTH]; SHA_CTX ctx; SHA1_Init(&ctx); SHA1_Update(&ctx, str, strlen); SHA1_Final(digest, &ctx); for (int i = 0; i < SHA_DIGEST_LENGTH; i++){ sprintf(&result[i*2], "%02x", (unsigned int)digest[i]); } }
md5.c文件中包含了md5和sha1兩個函數,後面會用來編譯到wasm。java
Tips: 1. 默認狀況下,Emscripten 生成的代碼只會調用 main() 函數,其它的函數將被視爲無用代碼。在一個函數名以前添加 EMSCRIPTEN_KEEPALIVE 可以防止這樣的事情發生。你須要導入 emscripten.h 庫來使用 EMSCRIPTEN_KEEPALIVE。 2. 內部實現調用的是openssl提供的函數,簡單封裝下直接調用便可。
我用的openssl版本是1.1.1d,地址: https://github.com/openssl/openssl/releases/tag/OpenSSL_1_1_1d
解壓後,進入openssl-OpenSSL_1_1_1d文件夾。編譯生成Makefile文件。jquery
emcmake ./Configure darwin64-x86_64-cc -no-asm --api=1.1.0
修改生成的Makefile文件,若是不修改,容易出現編譯錯誤。git
emmake make -j 12 build_generated libssl.a libcrypto.a mkdir -p ~/resource/openssl/libs cp -R include ~/resource/openssl/include cp libcrypto.a libssl.a ~/Downloads/openssl/libs
建立了一個openssl目錄,實際上是爲了在md5.c中引用靜態庫的位置。編譯成功後,文件夾下會出現libssl.a和libcrypto.a兩個文件,github
emcc md5.c -I ~/resource/openssl/include -L ~/resource/openssl/libs -lcrypto -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "ccall"]' -o md5.js
編譯成功後,會生成md5.js和md5.wasm兩個文件。web
Tips: Emscripten從v1.38開始,ccall/cwrap輔助函數默認沒有導出,在編譯時須要經過-s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']"選項顯式導出。
使用WebAssembly JS API調用wasm。md5和sha1的代碼都放在了md5.html中了,二者使用方式同樣,文中只貼md5相關代碼。代碼地址: https://github.com/likai1130/study/blob/master/wasm/openssl/demo/md5.html算法
<div> <div> <input type="file" id="md5files" style="display: none" onchange="md5fileImport();">計算md5 <input type="button" id="md5fileImport" value="導入"> </div> </div> <script src="jquery-3.5.1.min.js"></script> <script src="md5.js"></script> <script type='text/javascript'> Module = {}; const mallocByteBuffer = len => { const ptr = Module._malloc(len) const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, len) return heapBytes } //點擊導入按鈕,使files觸發點擊事件,而後完成讀取文件的操做 $("#md5fileImport").click(function() { $("#md5files").click(); }) function md5fileImport() { //獲取讀取我文件的File對象 var selectedFile = document.getElementById('md5files').files[0]; var name = selectedFile.name; //讀取選中文件的文件名 var size = selectedFile.size; //讀取選中文件的大小 console.log("文件名:" + name + "大小:" + size); var reader = new FileReader(); //讀取操做就是由它完成. reader.readAsArrayBuffer(selectedFile) reader.onload = function() { //當讀取完成後回調這個函數,而後此時文件的內容存儲到了result中,直接操做便可 console.log(reader.result); const md5 = Module.cwrap('md5', null, ['number', 'number']) const inBuffer = mallocByteBuffer(reader.result.byteLength) var ctx = new Uint8Array(reader.result) inBuffer.set(ctx) const outBuffer = mallocByteBuffer(32) md5(inBuffer.byteOffset,outBuffer.byteOffset,inBuffer.byteLength) console.log("md5值= ",Array.from(outBuffer).map(v => String.fromCharCode(v)).join('')) Module._free(inBuffer); Module._free(outBuffer); } } </script>
文件a.out,是個二進制數據
md5: 0d3c57ec65e81c7ff6da72472c68d95b
sha1: 9ef00799a4472c71f2177fd7254faaaadedb0807typescript
一個是程序計算的md5和sha1,一個是系統上openssl計算的md5和sha1,說明本次Webassembly編譯openssl的實踐是成功的。api
調用鏈以下:
md5.js (膠水代碼)<-----> md5.c <-----> openssl API
在整個實踐的過程當中,最使人頭疼的問題是數據通訊問題。在 C/C++ 和 JS 之間傳遞複雜數據結構很麻煩,須要操做內存來實現。
Javascript與C/C++交換數據
typescript #md5.wasm解析後的md5函數在wasm文件中的代碼 func $md5 (;3;) (export "md5") (param $var0 i32) (param $var1 i32) (param $var2 i32)
由於wasm 目前只能夠 import 和 export C 語言函數風格的 API,並且參數只有四種數據類型(i32, i64, f32, f64),都是數字,能夠理解爲赤裸裸的二進制編碼,無法直接傳遞複雜的類型和數據結構。因此在瀏覽器中這些高級類型的 API 必須靠 JS 來封裝,中間還須要一個機制實現跨語言轉換複雜的數據結構。
Module.buffer
不管編譯目標是asm.js仍是wasm,C/C++代碼眼中的內存空間實際上對應的都是Emscripten提供的ArrayBuffer對象:Module.buffer,C/C內存地址與Module.buffer數組下標一一對應。
function md5fileImport() { var selectedFile = document.getElementById('md5files').files[0]; var name = selectedFile.name; //讀取選中文件的文件名 var size = selectedFile.size; //讀取選中文件的大小 console.log("文件名:" + name + "大小:" + size); var reader = new FileReader(); //這是核心,讀取操做就是由它完成. reader.readAsArrayBuffer(selectedFile) ..... }
在代碼中咱們使用reader.readAsArrayBuffer()來讀取文件,返回的是ArrayBuffer數組。但仍是不能調用C函數,須要建立一個 typed array,如 Int8Array, UInt32Array,用其特定的格式做爲這段二進制數據的 view,從而進行讀寫操做。
Tips: C/C++代碼能直接經過地址訪問的數據所有在內存中(包括運行時堆、運行時棧),而內存對應Module.buffer對象,C/C代碼能直接訪問的數據事實上被限制在Module.buffer內部。
WebAssembly 的內存也是一個 ArrayBuffer,Emscripten 封裝的 Module 提供了 Module.HEAP八、Module.HEAPU8 等各類 view。附圖:
計算md5/sha1須要javascript將大量數據輸入到C/C++環境,而C/C++沒法預知數據塊的大小,此時能夠在JavaScript中分配內存並裝入數據,而後將數據指針傳入,調用C函數進行處理。
Tips: 這種用法之因此可行,核心緣由在於:Emscripten導出了C的malloc()/free()
我將分配內存空間的方法聲明成了公共方法。
Module = {}; const mallocByteBuffer = len => { const ptr = Module._malloc(len) const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, len) return heapBytes } function md5fileImport() { //獲取讀取我文件的File對象 var selectedFile = document.getElementById('md5files').files[0]; ...... var reader = new FileReader(); //這是核心,讀取操做就是由它完成. reader.readAsArrayBuffer(selectedFile) reader.onload = function() { //當讀取完成後回調這個函數,而後此時文件的內容存儲到了result中,直接操做便可 const md5 = Module.cwrap('md5', null, ['number', 'number']) const inBuffer = mallocByteBuffer(reader.result.byteLength) var ctx = new Uint8Array(reader.result) inBuffer.set(ctx) const outBuffer = mallocByteBuffer(32) md5(inBuffer.byteOffset,outBuffer.byteOffset,inBuffer.byteLength) console.log("md5值= ",Array.from(outBuffer).map(v => String.fromCharCode(v)).join('')) Module._free(inBuffer); Module._free(outBuffer); } }
Tips: C/C++的內存沒有gc機制,在JavaScript中使用malloc()函數分配的內存使用結束後,須要使用free()將其釋放。
此外,Emscripten還提供了AsciiToString()/stringToAscii()/UTF8ArrayToString()/stringToUTF8Array()等一系列輔助函數用於處理各類格式的字符串在各類存儲對象中的轉換,欲知詳情請自行參考膠水代碼。
基於wasm的openssl完整調用關係:
本次實踐過程當中遇到的技術問題就是數據通訊的問題,還有一個是思路上的問題,一直覺得把openssl總體編譯成.wasm文件,就能夠用了,事實證實還須要使用膠水代碼,才能在web中使用。那麼有個疑問.wasm文件本質上是個二進制文件,是否有工具能夠直接運行呢.wasm文件,WAPM(WebAssembly Package Manager) 這是WebAssembly的包管理工具,下一篇文章一塊兒來認識下WebAssembly包管理工具。
示例代碼地址:
WebAssembly API(中文,解決邏輯JS調用wasm問題。):
Emscripten 語法學習(解決C語言調用JS語法問題):
Openssl 編譯參考
Netwarps 由國內資深的雲計算和分佈式技術開發團隊組成,該團隊在金融、電力、通訊及互聯網行業有很是豐富的落地經驗。Netwarps 目前在深圳、北京均設立了研發中心,團隊規模30+,其中大部分爲具有十年以上開發經驗的技術人員,分別來自互聯網、金融、雲計算、區塊鏈以及科研機構等專業領域。Netwarps 專一於安全存儲技術產品的研發與應用,主要產品有去中心化文件系統(DFS)、去中心化計算平臺(DCP),致力於提供基於去中心化網絡技術實現的分佈式存儲和分佈式計算平臺,具備高可用、低功耗和低網絡的技術特色,適用於物聯網、工業互聯網等場景。公衆號:Netwarps