Browser-Side Crypto in Practice: Web Crypto API and SM2/SM3/SM4
Why Encrypt in the Browser?
Traditional flows send data to a server for crypto work—which creates privacy risk:
Traditional: plaintext → network → server encrypts → ciphertext back
Risk: plaintext may be intercepted; server may log data
Browser-side: plaintext → local encrypt → ciphertext
Benefit: plaintext never leaves the device
ToolsKu's 30+ crypto tools run entirely in the browser with zero uploads.
Web Crypto API: Native Browser Crypto
Capabilities
Web Crypto API is a W3C standard supported in all modern browsers:
// Feature detection
if (window.crypto?.subtle) {
// Web Crypto API available
}
| Category | Algorithms | Key sizes |
|---|---|---|
| Symmetric | AES-GCM, AES-CBC, AES-CTR | 128/192/256 |
| Asymmetric | RSA-OAEP, RSA-PSS, ECDSA | 2048/4096 |
| Key derivation | PBKDF2, HKDF, ECDH | - |
| Hash | SHA-1, SHA-256, SHA-384, SHA-512 | - |
| HMAC | HMAC (with hashes above) | - |
AES-256-GCM end-to-end
async function aesGcmEncrypt(plaintext: string, password: string) {
const enc = new TextEncoder();
// 1. Derive key from password (PBKDF2)
const salt = crypto.getRandomValues(new Uint8Array(16));
const keyMaterial = await crypto.subtle.importKey(
'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
);
// 2. Random IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// 3. Encrypt
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(plaintext)
);
// 4. Concatenate: salt + iv + ciphertext
return concatBuffers(salt, iv, ciphertext);
}
Security decisions
| Decision | Choice | Rationale |
|---|---|---|
| Symmetric mode | AES-GCM (not CBC) | Authenticated encryption, tamper-resistant |
| Key derivation | PBKDF2 + SHA-256 | Standard, widely supported |
| Iterations | 600,000 | OWASP 2023 minimum recommendation |
| IV length | 12 bytes (96 bits) | Recommended for GCM |
| Salt | 16 random bytes | Mitigates rainbow tables |
National Algorithms: JS Implementation Challenges
Algorithm map
| Algorithm | Type | International analog | ToolsKu |
|---|---|---|---|
| SM2 | Asymmetric encrypt/sign | ECDSA (P-256) | sm-crypto |
| SM3 | Hash | SHA-256 | sm-crypto |
| SM4 | Symmetric | AES-128 | sm-crypto |
Using sm-crypto
import { sm2, sm3, sm4 } from 'sm-crypto';
// SM2 key pair
const keyPair = sm2.generateKeyPairHex();
// publicKey: keyPair.publicKey (130 hex chars, uncompressed)
// privateKey: keyPair.privateKey (64 hex chars)
// SM2 encrypt (C1C3C4 mode, national standard)
const cipherMode = 1; // 1 = C1C3C4, 0 = C1C2C3
const ciphertext = sm2.doEncrypt(msg, keyPair.publicKey, cipherMode);
// SM2 decrypt
const plaintext = sm2.doDecrypt(ciphertext, keyPair.privateKey, cipherMode);
// SM3 hash
const hash = sm3('message');
// SM4 encrypt (ECB mode)
const key = '0123456789abcdeffedcba9876543210'; // 128-bit hex
const encrypted = sm4.encrypt(msg, key);
Performance comparison
| Operation | Web Crypto (AES-256-GCM) | sm-crypto (SM4) | Ratio |
|---|---|---|---|
| 1MB encrypt | 2ms | 45ms | 22x |
| 1MB decrypt | 2ms | 42ms | 21x |
| Key generation | 0.5ms | 3ms | 6x |
Web Crypto is 20×+ faster—native C/Rust (or hardware) under the hood vs pure JS big-integer math in sm-crypto.
Optimization strategies
- Web Workers: Offload SM2/SM4 to avoid blocking the UI thread
- Wasm acceleration: Community experiments compiling sm-crypto hot paths to Wasm
- Chunking: Encrypt large files in blocks with progress UI
Crypto Tool Architecture
Layering in ToolsKu
UI layer (React components)
↓
Crypto service layer (unified API)
↓
┌──────────────┬──────────────┐
│ Web Crypto │ sm-crypto │
│ (standard) │ (national) │
│ native │ pure JS │
└──────────────┴──────────────┘
Unified interface
interface CryptoService {
encrypt(data: Uint8Array, key: CryptoKeyInput): Promise<Uint8Array>;
decrypt(data: Uint8Array, key: CryptoKeyInput): Promise<Uint8Array>;
generateKey(): Promise<CryptoKeyOutput>;
}
Standard and national algorithms implement the same interface—UI stays agnostic.
File encryption memory management
async function encryptFile(file: File, password: string) {
// 1. Read file into memory
const buffer = await file.arrayBuffer();
// 2. Chunk large files (avoid OOM on single encrypt)
const CHUNK_SIZE = 64 * 1024 * 1024; // 64MB
if (buffer.byteLength > CHUNK_SIZE) {
return encryptFileInChunks(buffer, password, CHUNK_SIZE);
}
// 3. Encrypt
const result = await aesGcmEncrypt(new Uint8Array(buffer), password);
// 4. Clear sensitive data
buffer = null; // allow GC
return result;
}
Common Crypto Mistakes
Mistake 1: Base64 ≠ encryption
Base64("password123") = "5a+G56CBMTIz" ← reversible in a second
AES-256("password123") = "U2FsdGVkX1..." ← useless without the key
Base64 is encoding, not encryption. Anyone can decode it.
Mistake 2: MD5/SHA-1 are not "encryption"
Hashes are one-way—you cannot "decrypt" them. Use them for integrity, not confidentiality.
- MD5: broken—do not use for security
- SHA-1: collision attacks—avoid
- SHA-256/SM3: currently considered safe
Mistake 3: ECB mode is not secure
ECB: identical plaintext blocks → identical ciphertext (patterns leak)
CBC/GCM: identical plaintext blocks → different ciphertext (safe)
ToolsKu AES tools only offer GCM—no insecure ECB.
Summary
Browser-side crypto is production-ready today:
- Web Crypto API: Standard algorithms (AES/RSA/SHA) at near-native speed
- sm-crypto: National algorithms in pure JS for compliance
- Zero-upload: Keys and data never leave the device
ToolsKu offers AES encryption, RSA encryption, SM2 encryption, SM3 hash, SM4 encryption, and 30+ more tools—all processed locally in your browser.
Try these browser-local tools — no sign-up required →