← Back to Lessons Lesson 7 of 8
Intermediate security
Cryptographic Hashing
Introduction
Cryptographic hashing is a perfect use case for WebAssembly:
- CPU-intensive — benefits from Rust's performance (2-5x faster than JS)
- Client-side — sensitive data never leaves the browser
- Pure Rust — no dependency on Web Crypto API, works identically on all platforms
Setup
Add the sha2 crate to your Cargo.toml:
[dependencies]
wasm-bindgen = "0.2"
sha2 = "0.10"The sha2 crate is a pure Rust implementation — no C bindings, no system dependencies. It compiles to Wasm without any configuration.
How SHA-256 works (simplified)
- Input data is padded to a multiple of 512 bits
- Data is processed in 512-bit blocks through 64 rounds of compression
- Output is a fixed 256-bit (32-byte) hash — always the same length regardless of input size
Properties:
- Deterministic — same input always produces the same hash
- One-way — can't reverse a hash back to the original input
- Collision-resistant — practically impossible to find two inputs with the same hash
- Avalanche effect — changing one bit of input changes ~50% of output bits
Hashing strings
use sha2::{Sha256, Digest};
pub fn sha256_hash(input: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
result.iter()
.map(|byte| format!("{:02x}", byte))
.collect()
}Hashing files (raw bytes)
For file hashing, accept &[u8] which maps to Uint8Array in JavaScript:
#[wasm_bindgen]
pub fn hash_file(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize()
.iter()
.map(|byte| format!("{:02x}", byte))
.collect()
}JavaScript usage:
// Hash a file selected by the user
const file = document.getElementById('file-input').files[0];
const buffer = await file.arrayBuffer();
const hash = hash_file(new Uint8Array(buffer));
console.log("File SHA-256:", hash);Calling from JavaScript
import init, { sha256_hash, verify_integrity, sha256_bytes } from './pkg/crypto.js';
await init();
// Hash a string
const hash = sha256_hash("Hello, WebAssembly!");
console.log(hash);
// → "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
// Verify integrity
const valid = verify_integrity("Hello, WebAssembly!", hash);
console.log(valid); // → true
// Hash binary data
const bytes = new TextEncoder().encode("binary data");
const byteHash = sha256_bytes(bytes);Performance: Rust/Wasm vs JavaScript
| Implementation | 1MB hash | 10MB hash | 100MB hash |
|---|---|---|---|
| Pure JavaScript | ~15ms | ~150ms | ~1500ms |
| Rust/Wasm | ~3ms | ~30ms | ~300ms |
| Web Crypto API | ~2ms | ~20ms | ~200ms |
Rust/Wasm is ~5x faster than pure JS. The Web Crypto API is slightly faster (hardware-accelerated) but is async-only and less flexible.
Security considerations
- Never hash passwords directly — use a key derivation function (bcrypt, argon2) instead
- SHA-256 is not encryption — hashing is one-way; anyone can hash the same input
- Salt your hashes — prepend a random value to prevent rainbow table attacks
- Client-side hashing doesn't replace server-side validation — a malicious client can skip it
Try It
Add a function that hashes a file's contents (as &[u8]) and returns the hash. You could also try implementing HMAC for message authentication.
Try It
Chapter Quiz
Pass all questions to complete this lesson