基於 wasm 的 openssl 實踐

2021-02-20 Netwarps
上一篇文章分享了 WebAssembly 概念和基本使用,通過兩個代碼示例的分析對 WebAssembly 有了大致的了解。這一篇文章分享的是基於 WebAssembly 的加密工具實踐,我們就以 openssl 的摘要算法 md5 和 sha1 為例,在 Mac 上編譯 openSSL 到 WebAssembly。
在Mac上編譯openSSL到WebAssembly將 Openssl 編譯到 WebAssembly 整個流程是這樣的,md5.c文件–>emscripten編譯–>.wasm文件–>結合WebAssembly JS API–>瀏覽器中運行。

//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。

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 文件。

emcmake ./Configure  darwin64-x86_64-cc -no-asm --api=1.1.0

修改生成的 Makefile 文件,如果不修改,容易出現編譯錯誤。

emmake make -j 12 build_generated libssl.a libcrypto.amkdir -p ~/resource/openssl/libscp -R include ~/resource/openssl/includecp libcrypto.a libssl.a ~/Downloads/openssl/libs

創建了一個 openssl 目錄,其實是為了在 md5.c 中引用靜態庫的位置。編譯成功後,文件夾下會出現 libssl.a 和 libcrypto.a 兩個文件。

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 兩個文件。

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: 9ef00799a4472c71f2177fd7254faaaadedb0807



一個是程序計算的 md5 和 sha1,一個是系統上 openssl 計算的 md5 和 sha1,說明本次 Webassembly 編譯 openssl 的實踐是成功的。

md5.js (膠水代碼)<> md5.c <> openssl API

在整個實踐的過程中,最令人頭疼的問題是數據通信問題。在 C/C++ 和 JS 之間傳遞複雜數據結構很麻煩,需要操作內存來實現。

Javascript 與 C/C++ 交換數據

#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.HEAP8、Module.HEAPU8 等各種 view。附圖:

在 JavaScript 中訪問 C/C++ 內存

計算 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() 等一系列輔助函數用於處理各種格式的字符串在各種存儲對象中的轉換,欲知詳情請自行參考膠水代碼。

本次實踐過程中遇到的技術問題就是數據通信的問題,還有一個是思路上的問題,一直以為把 openssl 整體編譯成 .wasm 文件,就可以用了,事實證明還需要使用膠水代碼,才能在 web 中使用。那麼有個疑問 .wasm 文件本質上是個二進位文件,是否有工具可以直接運行呢 .wasm 文件,WAPM(WebAssembly Package Manager) 這是WebAssembly的包管理工具,下一篇文章一起來認識下WebAssembly 包管理工具。

https://github.com/likai1130/study/tree/master/wasm/openssl/demo

WebAssembly API(中文,解決邏輯JS調用wasm問題):https://developer.mozilla.org/zh-CN/docs/WebAssembly#API%E5%8F%82%E8%80%83

Emscripten 語法學習(解決C語言調用JS語法問題):

https://emscripten.org/docs/api_reference/emscripten.h.html#c.EM_ASM_

Openssl 編譯參考

https://github.com/wapm-packages/OpenSSL/blob/master/build.sh

相關焦點

  • wasm runtime 性能測試
    上一篇文章分享了 wasmer runtime,編譯好的 openssl.wasm 文件可以獨立運行,這篇文章分享 openssl.wasm 和原生 openssl 之間的性能對比。分別對 100/300/500 MB 進行多次加密解密,得到均值結果。注意,這裡有些模式並不是標準的,所以沒有全部模擬,僅供參考。#!
  • IVWEB玩轉wasm系列-揭秘wasm+h265直播播放器
    「WebAssembly,因其接近機器碼的特性和更小的文件體積,使用wasm文件相較於js會有編譯和加載時間更少的優勢,加上利用Emscripten等編譯器工具可以將c/c++編譯成wasm文件極大的拓展了前端的視野,從2017年推出以來,就一直是前端開發者們關注的熱點。> 現在,我們已經可以看到很多基於wasm遊戲/音視頻/web文件處理等方面的web應用。
  • Wasm與K8s如何雙劍合璧?
    比如,我們可以將 Nginx 的 WASM 鏡像作為基礎鏡像,基於這個鏡像可以構建包含不同 Web 內容的應用鏡像;我們可以利用 tag 對應用版本進行追蹤;利用 Docker Registry 進行應用分發;在這個過程我們還可以進一步利用數字籤名來保障安全的軟體供應鏈;我提供了一個技術原型示例項目,大家可以參考其中的例子來構建 WASM 容器鏡像。
  • Apache APISIX 擁抱 WASM 生態
    下面我們將結合 proxy-wasm-go-sdk 來講講怎麼用 WASM 實現注入自定義響應的功能。 步驟一:基於 proxy-wasm-go-sdk 編寫代碼 實現代碼(包含 `go.mod` 和其他)具體細節可參考:https://github.com/apache/apisix/tree/master/t/wasm這裡需要解釋下,雖然 proxy-wasm-go-sdk 這個項目帶了 Go 的名字,但它其實用的是 tinygo 而不是原生的 Go。
  • IVWEB玩轉wasm系列-純web視頻剪輯/轉換工具
    2. wasm重生這篇文章不是webassembly和emscripen的(以下簡稱wasm)的介紹文,關於wasm這裡只提及它的幾個核心關鍵詞,二進位字節碼,體積更小,運行更快,更多的信息可以參考WebAssembly 不完全指北。
  • openssl 筆記
    openssl.cnf 文件。創建自籤名CA 頒發用戶證書openssl genrsa -des3 -out client.key 1024openssl req -new -key client.key -out client.csropenssl ca -in client.csr -out client.crt -config .
  • 【Rust日報】 2020-02-17 WASM向量圖形 --wasm_svg_graphics 0.3.0
    WASM向量圖形 --wasm_svg_graphics 0.3.0一個用於通過WASM渲染SVG圖形的Rust
  • WASM·技術趨勢
    引用我超喜歡程序猿的一句diss用語:Talk is cheap ,    show me the code個人比較推薦學習rust來上手wasm ,@無界 看完以下的項目,你會喜歡上rust嗎?這是知乎上的一個問題:寫wasm項目選C++還是Rust?一般我會查找awesome來全面了解某個topic,比如awesome-rust。
  • openssl交叉編譯
    openssl,也應儘快升級到1.1.1版本。直接點擊上圖中的 openssl-1.1.1g.tar.gz 下載源碼壓縮包,通過瀏覽器完成下載,也可以使用 wget 方式下載,如下二、配置選項1、下載完成後,進行解壓縮:tar xzvf openssl-1.1.1g.tar.gz2、查看 INSTALL 及 NOTES.UNIX 文檔了解相關編譯安裝細節
  • 如何使用OpenSSL創建自籤名SSL證書
    基於這兩條考慮,考慮使用開源組件OpenSSL創建自籤名SSL證書。自己發給自己的證書,可不就想怎麼改就怎麼改。查看OpenSSL版本獲取最新版本OpenSSL解壓後進行編譯安裝配置,升級到最新版本tar -zxvf openssl-1.1.1k.tar.gz cd openssl-1.1.1k .
  • [號外] Blazor wasm 其實也挺快!
    之前第一篇的時候,因為沒有用任意配置,導致wa
  • Line在服務端 WASM的分享、用Rust和wasm實現 AI as a service-WebAssembly 周報0923
    添加 ERROR_ON_WASM_CHANGES_AFTER_LINK 選項,如果連接後我們需要在 wasm-emscripten-finalize 或者 wasm-opt 中執行任何工作,則添加 ERROR on wasm changes after _ link 選項。這可以驗證連結是達到最快速度,也不做 DWARF 重寫。
  • OpenSSL 在 Apache 和 Dovecot 下的使用(二)
    這些示例基於前面的教程; 請參閱最後的參考資料部分,了解本系列中以前的所有教程的連結。你需要配置 Postfix 以及 Dovecot 都使用 OpenSSL,我們將使用我們在OpenSSL 在 Apache 和 Dovecot 下的使用(一)[2]中創建的密鑰和證書。
  • RTSP H264/HEVC 流 Wasm 播放
    代碼: https://github.com/ikuokuo/rtsp-wasm-player相關模塊:# RTSP WebSocket ProxyRTSP/Webcam/File > FFmpeg open > Packets > WebSocket
  • 公鑰基礎設施(PKI)原理+OpenSSL實戰
    配置apt使用阿里源,並使用apt命令安裝openssl本次實驗基於OpenSSL 1.1.1,使用如下命令查看openssl版本號公鑰加密體系中priv.passroot@learn01:/root/work# echo talkingbigdata > priv.pass# 私鑰的密碼文件建議以pbkdf2(Password-Based Key Derivation Function)方式進行加密保存root@learn01:/root/work# openssl
  • Wasm 在物聯網、雲和多媒體領域發展現狀(首屆 WebAssembly Sumimit 解析下篇)
    本文我們將繼續回顧在大會下半場中,分享者為我們帶來的,Wasm 在現階段各類工程領域中的一些精彩實踐。Johnathan 為我們帶來了 Wasm 與物聯網設備相關的實踐分享。物聯網,即 「Internet of Thing」,我們一般簡稱為 IoT。
  • 前端每周清單:TensorFlow.js,深入了解wasm-bindgen
    前端每周清單專注大前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為新聞熱點、開發教程、工程實踐、深度閱讀、開源項目、巔峰人生等欄目。歡迎關注【前端之巔】微信公眾號(ID: frontshow),及時獲取前端每周清單。
  • Istio 1.12 引入 Wasm 插件配置 API 以擴展 Istio 生態
    (key, value) } return types.ActionContinue}// Perform rate limitingfunc (ctx *httpHeaders) OnHttpRequestHeaders(int, bool) types.Action { current := time.Now().UnixNano() // We use nanoseconds
  • Mesh7# wasm擴展Envoy使用詳解
    Envoy也提供的Filter機制來做這些功能,通常有以下方式:通過C++代碼自定義filter重新編譯Envoy第一種C++編譯學習成本過高,維護也困難,第二種適合用於實現簡單功能可以,第三種是重點發展方向通過其他語言編寫filter,通過wasm編譯運行嵌入在Envoy中運行,通過可移植的二進位指令實現,以下特性:流量進過