加密算法通常计算密集,用 Rust 实现既快又安全 (内存安全)。
加密操作的特点:
实测数据:
| 算法 | 数据大小 | JavaScript | Rust + WASM | 提升 |
|---|---|---|---|---|
| AES-256 | 10MB | 3500ms | 600ms | 5.8x |
| SHA-256 | 10MB | 2800ms | 420ms | 6.7x |
| Base64 | 10MB | 180ms | 45ms | 4x |
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 = true1use 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}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}适合:
不适合:
btoa 够用)