NiceLeeのBlog 用爱发电 bilibili~

Rust 使用wasm实现path签名sign

2022-11-25
nIceLee

阅读:


一般网络请求的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>

示例

https://nicennnnnnnlee.github.io/rust-wasm-digest/


内容
隐藏