一般网络请求的path后面都会跟一个
&sign=...
,用于验证该request的合法性。
通常情况下,这个步骤是隐藏在各种js加密混淆里的。
这里,我们尝试使用wasm将该步骤实现。
Quick Start
我们可以参考rustwasm,快速开始一个wasm的Hello world项目。
- 安装 rust 环境
- 安装 wasm-pack
- 运行
wasm-pack new hello-wasm
cd hello-wasm
- 运行
wasm-pack build
- 生成的wasm相关文件在
pkg
文件夹下
目标
我们要用Rust来大致实现下面这个东西。
// let salt = "asy8";
// let randomUint8Array = new Uint8Array(8);
// crypto.getRandomValues(randomUint8Array);
// let random = randomUint8Array.map((b) => b.toString(16).padStart(2, '0')).join('');
async function digestMessage(message, salt, random) {
const msgUint8 = new TextEncoder().encode(random + message + salt); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
return random + hashHex;
}
Rust实现
我们将要在rust里面调用某些js内置函数:
window.crypto.subtle.digest(algorithm, data)
window.crypto.getRandomValues(dataUint8Array)
console.log(content)
我们将暴露给js某些rust函数实现:
async function sign(data)
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = ["window", "crypto", "subtle"])]
async fn digest(algorithm: &str, data: js_sys::Uint8Array) -> JsValue;
#[wasm_bindgen(js_namespace = ["window", "crypto"])]
fn getRandomValues(data: &mut [u8]);
// #[wasm_bindgen(js_namespace = ["window"])]
// fn buffer2str(buffer: JsValue) -> JsValue;
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub async fn sign(url: &str) -> JsValue {
let salt = "asy8";
unsafe {
let mut random = [0u8; 8];
getRandomValues(&mut random);
let random_str = hex::encode(random.to_vec());
let data_to_hash = random_str + url + salt;
// log(&data_to_hash);
let data_to_hash = js_sys::Uint8Array::view(data_to_hash.as_bytes());
let result = digest("SHA-256", data_to_hash).await;
let result = js_sys::ArrayBuffer::from(result);
let result = js_sys::Uint8Array::new(&result);
let result = result.to_vec();
let result = hex::encode(random.to_vec()) + &hex::encode(result);
// log(&result);
JsValue::from_str(&result)
// let buffer = digest("SHA-256", data_to_hash).await;
// buffer2str(buffer)
}
}
如何使用
默认生成的是bundler
,可以像js模块一样引入并使用,这个比较简单,没啥好讲。
这里参考without-a-bundler,如何在没有Webpack等打包工具的情况下,生成可以直接在html里使用的实现。
--target web
wasm-pack build –target web
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Wasm Sign</title>
<style>
.center {
width: 100%;
text-align: center;
margin-top: 50px;
}
</style>
</head>
<body>
<div class="center">
<div>
<span>Data: </span>
<input type="text" id="dataInput" size="100" />
</div>
<div>
<span>Sign: </span>
<input type="text" readonly disabled="disabled" id="dataOutput" size="100" />
</div>
<input type="button" value="Get Sign" onclick="genSign();" />
</div>
<script type="module">
import { default as wasm, sign } from "./pkg/wasm_digest.js";
await wasm();
let dataInput = document.getElementById("dataInput");
let dataOutput = document.getElementById("dataOutput");
window.genSign = function () {
let data = dataInput.value;
sign(data).then(sign => dataOutput.value = sign);
}
</script>
</body>
</html>
--target no-modules
wasm-pack build –target no-modules –out-name without_a_bundler_no_modules
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Wasm Sign</title>
<style>
.center {
width: 100%;
text-align: center;
margin-top: 50px;
}
</style>
</head>
<body>
<div class="center">
<div>
<span>Data: </span>
<input type="text" id="dataInput" size="100" />
</div>
<div>
<span>Sign: </span>
<input type="text" readonly disabled="disabled" id="dataOutput" size="100" />
</div>
<input type="button" value="Get Sign" onclick="genSign();" />
</div>
<!-- Include the JS generated by `wasm-pack build` -->
<script src='pkg/without_a_bundler_no_modules.js'></script>
<script>
// Like with the `--target web` output the exports are immediately
// available but they won't work until we initialize the module. Unlike
// `--target web`, however, the globals are all stored on a
// `wasm_bindgen` global. The global itself is the initialization
// function and then the properties of the global are all the exported
// functions.
//
// Note that the name `wasm_bindgen` can be configured with the
// `--no-modules-global` CLI flag
const { sign } = wasm_bindgen;
async function init() {
await wasm_bindgen('./pkg/without_a_bundler_no_modules_bg.wasm');
}
init();
let dataInput = document.getElementById("dataInput");
let dataOutput = document.getElementById("dataOutput");
function genSign() {
let data = dataInput.value;
sign(data).then(sign => dataOutput.value = sign);
}
</script>
</body>
</html>