上一篇文章分享了WebAssembly概念和根本应用,通过两个代码示例的剖析对WebAssembly有了大抵的理解。这一篇文章分享的是基于WebAssembly的加密工具实际,咱们就以openssl的摘要算法md5和sha1为例,在Mac上编译openSSL到WebAssembly。

环境

  • Emscripten 版本 2.0.3
  • Openssl 版本1.1.1d
  • 浏览器 版本 85.0.4183.121(正式版本) (64 位)

概述

  • 在Mac上编译openSSL到WebAssembly
  • 遇到的问题
  • 总结

一、在Mac上编译openSSL到WebAssembly

将Openssl编译到WebAssembly整个流程是这样的,md5.c文件–>emscripten编译–>.wasm文件–>联合WebAssembly JS API–>浏览器中运行。

1. md5.c文件
//md5.c#include <emscripten.h>#include <openssl/md5.h>#include <openssl/sha.h>#include <string.h>#include <stdio.h>EMSCRIPTEN_KEEPALIVEvoid 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_KEEPALIVEvoid 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提供的函数,简略封装下间接调用即可。
2. Emscripten编译
下载openssl,生成Makefile

我用的openssl版本是1.1.1d,地址: https://github.com/openssl/op...
解压后,进入openssl-OpenSSL_1_1_1d文件夹。编译生成Makefile文件。

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

批改生成的Makefile文件,如果不批改,容易呈现编译谬误。

  • 将CROSS_COMPILE=/usr/local/Cellar/emscripten/1.38.44/libexec/em 改为 CROSS_COMPILE=
  • 将 CNF_CFLAGS=-arch x86_64 改为 CNF_CFLAGS=
编译openssl
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两个文件,

编译wasm
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']"选项显式导出。
3. 调用wasm文件

应用WebAssembly JS API调用wasm。md5和sha1的代码都放在了md5.html中了,两者应用形式一样,文中只贴md5相干代码。代码地址: https://github.com/likai1130/...

<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>
4. 浏览器中运行

文件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++替换数据

    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.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()等一系列辅助函数用于解决各种格局的字符串在各种存储对象中的转换,欲知详情请自行参考胶水代码。

三、总结

基于wasm的openssl残缺调用关系:

本次实际过程中遇到的技术问题就是数据通信的问题,还有一个是思路上的问题,始终认为把openssl整体编译成.wasm文件,就能够用了,事实证明还须要应用胶水代码,能力在web中应用。那么有个疑难.wasm文件实质上是个二进制文件,是否有工具能够间接运行呢.wasm文件,WAPM(WebAssembly Package Manager) 这是WebAssembly的包管理工具,下一篇文章一起来意识下WebAssembly包管理工具。

参考资料

  • 示例代码地址:

    • https://github.com/likai1130/...
  • WebAssembly API(中文,解决逻辑JS调用wasm问题。):

    • https://developer.mozilla.org...
  • Emscripten 语法学习(解决C语言调用JS语法问题):

    • https://emscripten.org/docs/a...
  • Openssl 编译参考

    • https://github.com/wapm-packa...

Netwarps 由国内资深的云计算和分布式技术开发团队组成,该团队在金融、电力、通信及互联网行业有十分丰盛的落地教训。Netwarps 目前在深圳、北京均设立了研发核心,团队规模30+,其中大部分为具备十年以上开发教训的技术人员,别离来自互联网、金融、云计算、区块链以及科研机构等业余畛域。
Netwarps 专一于平安存储技术产品的研发与利用,次要产品有去中心化文件系统(DFS)、去中心化计算平台(DCP),致力于提供基于去中心化网络技术实现的分布式存储和分布式计算平台,具备高可用、低功耗和低网络的技术特点,实用于物联网、工业互联网等场景。
公众号:Netwarps