加密与安全

加密算法通常计算密集,用 Rust 实现既快又安全 (内存安全)。

为什么需要 WASM

加密操作的特点:

  • 大量位运算和循环
  • 需要处理大块数据
  • 对性能要求高
  • Rust 的内存安全避免常见漏洞

性能对比

实测数据:

算法 数据大小 JavaScript Rust + WASM 提升
AES-256 10MB 3500ms 600ms 5.8x
SHA-256 10MB 2800ms 420ms 6.7x
Base64 10MB 180ms 45ms 4x

Rust 实现

Cargo.toml

1[package]
2name = "crypto-wasm"
3version = "0.1.0"
4edition = "2021"
5
6[lib]
7crate-type = ["cdylib"]
8
9[dependencies]
10wasm-bindgen = "0.2"
11sha2 = "0.10"
12aes = "0.8"
13ctr = "0.9"
14
15[profile.release]
16opt-level = 3
17lto = true

核心代码

1use wasm_bindgen::prelude::*;
2use sha2::{Sha256, Digest};
3
4// SHA-256 哈希
5#[wasm_bindgen]
6pub fn sha256(data: &[u8]) -> Vec<u8> {
7    let mut hasher = Sha256::new();
8    hasher.update(data);
9    hasher.finalize().to_vec()
10}
11
12#[wasm_bindgen]
13pub fn sha256_hex(data: &[u8]) -> String {
14    let hash = sha256(data);
15    hash.iter()
16        .map(|b| format!("{:02x}", b))
17        .collect()
18}
19
20// Base64 编码
21const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
22
23#[wasm_bindgen]
24pub fn base64_encode(data: &[u8]) -> String {
25    let mut result = String::with_capacity((data.len() + 2) / 3 * 4);
26
27    for chunk in data.chunks(3) {
28        let mut buf = [0u8; 3];
29        for (i, &byte) in chunk.iter().enumerate() {
30            buf[i] = byte;
31        }
32
33        let b1 = (buf[0] >> 2) as usize;
34        let b2 = (((buf[0] & 0x03) << 4) | (buf[1] >> 4)) as usize;
35        let b3 = (((buf[1] & 0x0f) << 2) | (buf[2] >> 6)) as usize;
36        let b4 = (buf[2] & 0x3f) as usize;
37
38        result.push(BASE64_CHARS[b1] as char);
39        result.push(BASE64_CHARS[b2] as char);
40        result.push(if chunk.len() > 1 { BASE64_CHARS[b3] as char } else { '=' });
41        result.push(if chunk.len() > 2 { BASE64_CHARS[b4] as char } else { '=' });
42    }
43
44    result
45}
46
47// AES-256-CTR 加密
48use aes::Aes256;
49use aes::cipher::{KeyInit, StreamCipher};
50use ctr::Ctr64BE;
51
52type Aes256Ctr = Ctr64BE<Aes256>;
53
54#[wasm_bindgen]
55pub fn aes_encrypt(data: &[u8], key: &[u8], nonce: &[u8]) -> Result<Vec<u8>, JsValue> {
56    if key.len() != 32 {
57        return Err(JsValue::from_str("密钥必须是 32 字节"));
58    }
59    if nonce.len() != 16 {
60        return Err(JsValue::from_str("Nonce 必须是 16 字节"));
61    }
62
63    let mut cipher = Aes256Ctr::new(key.into(), nonce.into());
64    let mut result = data.to_vec();
65    cipher.apply_keystream(&mut result);
66
67    Ok(result)
68}
69
70// CTR 模式加解密相同
71#[wasm_bindgen]
72pub fn aes_decrypt(data: &[u8], key: &[u8], nonce: &[u8]) -> Result<Vec<u8>, JsValue> {
73    aes_encrypt(data, key, nonce)
74}
75
76// 密码强度检测
77#[wasm_bindgen]
78pub fn password_strength(password: &str) -> u32 {
79    let mut score = 0;
80
81    // 长度
82    score += match password.len() {
83        0..=5 => 0,
84        6..=7 => 10,
85        8..=11 => 20,
86        12..=15 => 30,
87        _ => 40,
88    };
89
90    // 字符类型
91    let has_lower = password.chars().any(|c| c.is_lowercase());
92    let has_upper = password.chars().any(|c| c.is_uppercase());
93    let has_digit = password.chars().any(|c| c.is_numeric());
94    let has_special = password.chars().any(|c| !c.is_alphanumeric());
95
96    if has_lower { score += 10; }
97    if has_upper { score += 15; }
98    if has_digit { score += 15; }
99    if has_special { score += 20; }
100
101    score.min(100)
102}
103
104// 生成随机字节
105#[wasm_bindgen]
106pub fn random_bytes(length: usize) -> Vec<u8> {
107    use getrandom::getrandom;
108    let mut buf = vec![0u8; length];
109    getrandom(&mut buf).expect("无法生成随机数");
110    buf
111}

JavaScript 集成

1import init, {
2  sha256_hex,
3  base64_encode,
4  aes_encrypt,
5  aes_decrypt,
6  password_strength,
7  random_bytes,
8} from "./pkg/crypto_wasm.js";
9
10await init();
11
12// 文本转字节
13function textToBytes(text) {
14  return new TextEncoder().encode(text);
15}
16
17// 字节转文本
18function bytesToText(bytes) {
19  return new TextDecoder().decode(bytes);
20}
21
22// 计算哈希
23function calcHash() {
24  const text = "Hello World";
25  const data = textToBytes(text);
26
27  const hash = sha256_hex(data);
28  console.log(`SHA-256: ${hash}`);
29}
30
31// 加密文件
32async function encryptFile(file) {
33  const data = new Uint8Array(await file.arrayBuffer());
34
35  // 生成随机密钥和 nonce
36  const key = random_bytes(32);
37  const nonce = random_bytes(16);
38
39  const start = performance.now();
40  const encrypted = aes_encrypt(data, key, nonce);
41  const duration = performance.now() - start;
42
43  console.log(`加密 ${(data.length / 1024 / 1024).toFixed(2)} MB`);
44  console.log(`耗时: ${duration.toFixed(2)} ms`);
45  console.log(
46    `速度: ${((data.length / 1024 / 1024 / duration) * 1000).toFixed(2)} MB/s`
47  );
48
49  // 保存: nonce + 密文
50  const result = new Uint8Array(nonce.length + encrypted.length);
51  result.set(nonce, 0);
52  result.set(encrypted, nonce.length);
53
54  return { result, key };
55}
56
57// 解密文件
58async function decryptFile(encryptedData, key) {
59  // 提取 nonce 和密文
60  const nonce = encryptedData.slice(0, 16);
61  const ciphertext = encryptedData.slice(16);
62
63  const decrypted = aes_decrypt(ciphertext, key, nonce);
64  return decrypted;
65}
66
67// 检测密码强度
68function checkPassword(password) {
69  const strength = password_strength(password);
70
71  let level, color;
72  if (strength < 30) {
73    level = "弱";
74    color = "red";
75  } else if (strength < 60) {
76    level = "中";
77    color = "orange";
78  } else if (strength < 80) {
79    level = "强";
80    color = "green";
81  } else {
82    level = "很强";
83    color = "darkgreen";
84  }
85
86  console.log(`密码强度: ${level} (${strength}/100)`);
87}

注意事项

密钥管理

安全的随机数生成:

1// 对 - 使用 Web Crypto API
2const key = random_bytes(32);
3
4// 错 - 不要用 Math.random()
5const badKey = Array.from({ length: 32 }, () =>
6  Math.floor(Math.random() * 256)
7);

密钥存储:

1// 敏感密钥不要硬编码
2// 可以从用户输入派生
3async function deriveKey(password, salt) {
4  const encoder = new TextEncoder();
5  const keyMaterial = await crypto.subtle.importKey(
6    "raw",
7    encoder.encode(password),
8    { name: "PBKDF2" },
9    false,
10    ["deriveBits", "deriveKey"]
11  );
12
13  return await crypto.subtle.deriveKey(
14    {
15      name: "PBKDF2",
16      salt: encoder.encode(salt),
17      iterations: 100000,
18      hash: "SHA-256",
19    },
20    keyMaterial,
21    { name: "AES-GCM", length: 256 },
22    true,
23    ["encrypt", "decrypt"]
24  );
25}

安全编程

常量时间比较 (防时序攻击):

1fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
2    if a.len() != b.len() {
3        return false;
4    }
5
6    let mut result = 0u8;
7    for i in 0..a.len() {
8        result |= a[i] ^ b[i];
9    }
10
11    result == 0
12}

清理敏感数据:

1pub fn process_password(mut password: Vec<u8>) {
2    // 使用密码...
3
4    // 用完立即清零
5    for byte in &mut password {
6        *byte = 0;
7    }
8}

性能优化

批量处理:

1// 对 - 一次处理整个文件
2const encrypted = aes_encrypt(fileData, key, nonce);
3
4// 错 - 频繁调用
5for (let i = 0; i < fileData.length; i += 1024) {
6  const chunk = fileData.slice(i, i + 1024);
7  aes_encrypt(chunk, key, nonce); // 太慢
8}

实际应用

端到端加密聊天

1class SecureChat {
2  constructor() {
3    this.privateKey = random_bytes(32);
4  }
5
6  async encryptMessage(message, recipientPublicKey) {
7    // 计算共享密钥 (实际应该用 ECDH)
8    const sharedKey = this.computeSharedKey(recipientPublicKey);
9    const nonce = random_bytes(16);
10    const data = textToBytes(message);
11
12    const encrypted = aes_encrypt(data, sharedKey, nonce);
13
14    return {
15      nonce: base64_encode(nonce),
16      ciphertext: base64_encode(encrypted),
17    };
18  }
19
20  async decryptMessage(encrypted, senderPublicKey) {
21    const sharedKey = this.computeSharedKey(senderPublicKey);
22    const nonce = base64_decode(encrypted.nonce);
23    const ciphertext = base64_decode(encrypted.ciphertext);
24
25    const decrypted = aes_decrypt(ciphertext, sharedKey, nonce);
26    return bytesToText(decrypted);
27  }
28}

文件完整性校验

1async function verifyFile(file, expectedHash) {
2  const data = new Uint8Array(await file.arrayBuffer());
3  const hash = sha256_hex(data);
4
5  if (hash === expectedHash) {
6    console.log("文件完整,未被篡改");
7    return true;
8  } else {
9    console.log("警告: 文件已被修改!");
10    return false;
11  }
12}

什么时候用

适合:

  • 大文件加密 (>1MB)
  • 批量哈希计算
  • 实时加密通信
  • 密码学运算

不适合:

  • 简单的 Base64 (原生 btoa 够用)
  • 单次加密操作
  • 已有 Web Crypto API 的场景