← 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)

  1. Input data is padded to a multiple of 512 bits
  2. Data is processed in 512-bit blocks through 64 rounds of compression
  3. 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