2fa-wasm
v1.1.1
Published
Biblioteca 2FA zero-allocation (HOTP/TOTP/Base32, RFC 4226/6238/4648) en AssemblyScript/WebAssembly con wrapper TypeScript. Compatible con otpauth:// (Google Authenticator), validación constant-time, ~478K TOTP/s.
Downloads
442
Maintainers
Readme
🔐 2FA WASM
Biblioteca de autenticación de dos factores (2FA) ultra-rápida y de alto rendimiento compilada en WebAssembly (WASM) utilizando AssemblyScript y empaquetada con un wrapper TypeScript Zero-Allocation.
Soporta HOTP (RFC 4226), TOTP (RFC 6238) y Base32 (RFC 4648) sobre HMAC-SHA1 / SHA256 / SHA512 —con validación de tokens en tiempo constante (a prueba de timing attacks) y compatibilidad total con otpauth:// (Google Authenticator, Authy, etc.)— con rendimiento extremo (~478 K TOTP/s rotando los 3 algoritmos) y 0 % de presión sobre el Garbage Collector (GC).
🚀 Características Principales
- ⚡ Máximo Rendimiento: Núcleo HMAC desplegado en WebAssembly con ~478 000 TOTP/s (rotando SHA-1/256/512) y memoria completamente plana bajo carga.
- 🧹 Zero-Alloc (Cero Alocaciones GC): Alocador arena estático (
memory.data+save/restore) y contextos hash como struct sobre puntero (changetype, sinnewen el heap) —WASM Δ 0 Bverificado bajo 500 000 operaciones. - 🌐 Multiplataforma: Funciona sin modificaciones en Node.js, Bun, Deno y Navegadores Web (Vite, Webpack, etc.).
- 🛡️ Seguridad Crypto:
- HMAC-SHA1 / SHA256 / SHA512 (RFC 2104) como primitiva de HOTP/TOTP.
- Validación constant-time de tokens: comparación por XOR acumulativo sin early-exit, inmune a ataques de timing.
- Correctitud validada contra vectores oficiales (RFC 4226 Ap. D, RFC 6238 Ap. B, RFC 4648) y
node:crypto(36/36 tests).
- 🔑 HOTP / TOTP: generadores y validadores con ventana de tolerancia, 4–8 dígitos, y los 3 algoritmos (SHA-1 por compatibilidad, SHA-256/512 para mayor seguridad).
- 📱 Compatibilidad
otpauth://:toString()genera URIs escaneables yURI.parse()las reconstruye — integración directa con Google Authenticator, Authy, 1Password, etc. - 🔐 Secrets seguros: generación aleatoria con
crypto.getRandomValuesy conversiones Base32/hex. - 📦 Binario Compacto: ~14 KB de WebAssembly optimizado.
📦 Instalación
npm install 2fa-wasmO con Bun / Yarn / pnpm:
bun add 2fa-wasm💻 Guía de Uso
1. Inicializar la biblioteca
import { TwoFaWasm } from "2fa-wasm"
// Carga automática desde la URL por defecto (navegador / bundler)
await TwoFaWasm.load()
// O cargando desde un buffer binario explícito (útil para Node.js / Bun):
// import { readFileSync } from "node:fs"
// const wasmBuffer = readFileSync("node_modules/2fa-wasm/dist/2fa.wasm")
// await TwoFaWasm.fromBuffer(wasmBuffer)La carga se realiza una sola vez. Después puedes usar
TOTP/HOTP/Secret/Base32/URIdirectamente (usan el singleton global).
2. TOTP (Time-based OTP — RFC 6238)
El caso de uso más común (Google Authenticator y compatibles).
import { TwoFaWasm, TOTP, Secret } from "2fa-wasm"
await TwoFaWasm.load()
// 🔐 Genera un secret nuevo (20 bytes, típico para SHA-1)
const secret = new Secret()
console.log(secret.base32) // → "JBSWY3DPEHPK3PXP…" (para mostrar al usuario / QR)
// 🔑 Genera el código TOTP actual (6 dígitos)
const code = TOTP.generate({ secret })
console.log(code) // → "123456"
// ✅ Valida un código ingresado por el usuario (ventana ±1 por defecto)
const delta = TOTP.validate({ token: code, secret })
console.log(delta) // → 0 (período exacto) | ±1 (ventana) | null (inválido)
// ⏱️ Segundos restantes del período actual
const totp = new TOTP({ secret, period: 30 })
console.log(totp.remaining()) // → 18🛡️ Para mayor seguridad usa SHA-256/512 y un secret de 32/64 bytes:
new TOTP({ secret: new Secret({ size: 32 }), algorithm: "SHA256" }).
3. HOTP (Counter-based OTP — RFC 4226)
import { TwoFaWasm, HOTP, Secret } from "2fa-wasm"
await TwoFaWasm.load()
const secret = Secret.fromBase32("JBSWY3DPEHPK3PXP")
// 🔑 Genera el código HOTP para un counter (vector RFC 4226)
const code = HOTP.generate({ secret, counter: 0 })
console.log(code) // → "755224"
// ✅ Valida con ventana de tolerancia (±window)
const delta = HOTP.validate({ token: "287082", secret, counter: 0, window: 1 })
console.log(delta) // → 1 (el token corresponde al counter 1, dentro de la ventana)
// 🔄 API con estado: el counter se auto-incrementa en cada generate()
const hotp = new HOTP({ secret, counter: 0 })
hotp.generate() // counter 0 → luego incrementa a 1
hotp.generate() // counter 1 → luego incrementa a 24. Secret (gestión de la clave compartida)
import { TwoFaWasm, Secret } from "2fa-wasm"
await TwoFaWasm.load()
// 🎲 Aleatorio (20 bytes por defecto; 32/64 para SHA-256/512)
const s1 = new Secret()
const s2 = new Secret({ size: 32 })
// 📥 Desde un Base32 existente (pegado por el usuario)
const s3 = Secret.fromBase32("JBSWY3DPEHPK3PXP")
console.log(s3.base32) // → "JBSWY3DPEHPK3PXP"
console.log(s3.hex) // → "48656c6c6f21deadbeef"
console.log(s3.bytes) // → Uint8Array (bytes crudos)5. Base32 (RFC 4648)
import { TwoFaWasm, Base32 } from "2fa-wasm"
await TwoFaWasm.load()
const encoded = Base32.encode(new TextEncoder().encode("foobar"))
console.log(encoded) // → "MZXW6YTBOI"
const decoded = Base32.decode("MZXW6YTBOI") // → Uint8Array (bytes de "foobar")
// 🧹 Decode tolerante: acepta lowercase, espacios y padding (típico de secrets pegados a mano)
Base32.decode("mzxw 6ytb oi======") // → mismos bytes6. URI otpauth:// (compatible con Google Authenticator)
Genera y parsea URIs estándar para códigos QR y migración entre apps.
import { TwoFaWasm, TOTP, URI } from "2fa-wasm"
await TwoFaWasm.load()
const totp = new TOTP({
issuer: "MiApp",
label: "[email protected]",
secret: "JBSWY3DPEHPK3PXP",
algorithm: "SHA1",
digits: 6,
period: 30
})
// 📱 Genera la URI otpauth:// (para el código QR)
const uri = totp.toString()
// → "otpauth://totp/MiApp:usuario%40ejemplo.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&period=30&issuer=MiApp"
// 📥 Reconstruye desde una URI (ej. escaneada o importada)
const parsed = URI.parse(uri)
console.log(parsed.issuer) // → "MiApp"
console.log(parsed.generate()) // → código TOTP actual
URI.parse()devuelve una instanciaHOTPoTOTPsegún el tipo de URI, lista paragenerate()/validate().
7. API de bajo nivel (TwoFaWasm)
Acceso directo a las primitivas del WASM (uso avanzado).
import { TwoFaWasm } from "2fa-wasm"
const wasm = await TwoFaWasm.load()
// 🔏 HMAC directo (SHA1 / SHA256 / SHA512)
const mac = wasm.hmac("SHA256", clave, mensaje) // → Uint8Array
// ⏱️ Counter y segundos restantes de TOTP
const counter = wasm.totpCounter(30) // counter del período actual
const remaining = wasm.totpRemaining(30) // segundos restantes
// 🔑 Generación/validación raw (bytes, sin formatear)
const code = wasm.hotpGenerate(secretBytes, 0, 6, "SHA1") // → "755224"
const delta = wasm.totpValidate("123456", secretBytes, Date.now(), 30, 6, 1, "SHA1")8. Utilidades
import { bytesToHex, hexToBytes, toUint8Array, getRandomBytes } from "2fa-wasm"
const hex = bytesToHex(new Uint8Array([0xde, 0xad, 0xbe, 0xef])) // "deadbeef"
const bytes = hexToBytes("deadbeef") // Uint8Array(4)
const utf8 = toUint8Array("texto") // string → Uint8Array (UTF-8)
const random = getRandomBytes(32) // 32 bytes aleatorios (crypto-safe)📊 Benchmarks de Rendimiento
Pruebas ejecutadas en Bun (JavaScriptCore) sobre un procesador x86-64 moderno, en single-thread:
| Operación | Rendimiento | Memoria | | ------------------------------------ | ---------------------- | -------------- | | TOTP generate (3 algoritmos rotados) | ~478 K ops/s | — | | Estrés 500 000 ops | estable (~478 K ops/s) | WASM Δ 0 B | | Heap JS tras 500 000 ops | — | Δ ~0.02 MB |
Rondas de estrés (zero-alloc verificado):
| Iteraciones | Ops/seg | Heap Δ | WASM Δ | | ----------- | ------- | ------- | ------- | | 10 000 | 328 185 | 0.00 MB | 0 B | | 50 000 | 477 666 | 0.00 MB | 0 B | | 100 000 | 486 251 | 0.00 MB | 0 B | | 500 000 | 478 387 | 0.02 MB | 0 B |
El throughput se estabiliza (~478 K ops/s) en lugar de degradarse, y la memoria lineal del WASM nunca crece (
Δ 0 B) — la garantía zero-alloc se cumple bajo carga real, no solo en el caso feliz.
Validación de correctitud (36/36 tests):
| Estándar | Cobertura |
| ----------------------- | -------------------------------------------------- |
| HOTP — RFC 4226, Ap. D | 10 contadores (SHA-1, 6 dígitos) + ventana/delta |
| TOTP — RFC 6238, Ap. B | 6 timestamps × SHA-1/256/512 (8 dígitos) |
| Base32 — RFC 4648 | vectores + tolerancia (padding/lowercase/espacios) |
| HMAC — vs node:crypto | SHA-1/256/512 + clave larga (> bloque) |
🏗️ Arquitectura Zero-Allocation
- Alocador arena estático: 32 KB reservados en el data segment del WASM en tiempo de compilación (
memory.data), consave()/restore()por operación — ninguna reserva toca el heap del runtime. - Contextos como struct sobre puntero: los contextos hash (SHA-1/256/512) se superponen sobre el scratchpad vía
changetype(sinnew), eliminando por completo las alocaciones en el heap. - Validación constant-time:
hotp_validate_rawcompara el token generado con el recibido mediante XOR + OR acumulativo de longitud fija (sin early-exit), evitando fugas por timing. - Única asignación: el string del código OTP que se entrega al usuario (inevitable); todo lo demás vive en el scratchpad.
- Resultado verificado:
WASM Δ 0 By heap estable bajo 500 000 operaciones.
🛠️ Comandos de Desarrollo
# Compilar binario WASM (AssemblyScript) + bundle TypeScript (esbuild minificado)
bun run build
# Compilar solo el binario WASM
bun run asbuild # release → dist/2fa.wasm
bun run asbuild:debug # debug → build/debug.wasm + .wat + sourcemap
# Verificación de tipos
bun run typecheck
# Tests (suite de correctitud + estrés zero-alloc)
bun test📁 Estructura del Proyecto
2fa-wasm/
├── assembly/ # Núcleo en AssemblyScript (→ WebAssembly)
│ ├── index.ts # Exportaciones WASM (hmac, base32, hotp, totp)
│ ├── memory.ts # Alocador arena estático (Memory: alloc/save/restore)
│ ├── sha1.ts # SHA-1 (struct sobre puntero, compress desplegado)
│ ├── sha2/
│ │ ├── sha256.ts # SHA-256 (sliding-window)
│ │ ├── sha512.ts # SHA-512 (sliding-window)
│ │ ├── constants.ts # Constantes K e IV
│ │ └── common.ts # Helpers (bswap, Ch, Maj, Sigma, sigma)
│ ├── hmac.ts # Dispatch HMAC interno (SHA1/256/512)
│ ├── hotp.ts # HOTP (RFC 4226, validación constant-time)
│ ├── totp.ts # TOTP (RFC 6238)
│ └── base32.ts # Base32 (RFC 4648, decode tolerante)
├── src/ # Wrapper en TypeScript
│ ├── index.ts # Punto de entrada (re-exports)
│ ├── twofa-wasm.ts # Cargador (load/fromUrl/fromBuffer) + API bajo nivel
│ ├── allocator.ts # WasmAllocator (caché de heap, zero-alloc)
│ ├── base32.ts # Base32
│ ├── secret.ts # Secret (bytes/base32/hex)
│ ├── hotp.ts # HOTP + interfaces
│ ├── totp.ts # TOTP + interfaces
│ ├── uri.ts # Parser otpauth://
│ └── types.ts # Tipos (HashAlgorithm, configs) + utilidades
├── test/
│ ├── index.test.ts # Suite de correctitud (RFC 4226/6238/4648, node:crypto)
│ └── stress.test.ts # Estrés zero-alloc (WASM Δ 0 B)
├── dist/ # Build (generado)
├── asconfig.json # Configuración de AssemblyScript
├── package.json
├── tsconfig.json
├── LICENSE # Apache-2.0
└── README.md📜 Licencia
Apache License 2.0 © Edison Manrique
Exención de responsabilidad: este software se proporciona "tal cual" (AS IS), sin garantía de ningún tipo. Aunque HOTP, TOTP, HMAC y Base32 son estándares (RFC 4226 / RFC 6238 / RFC 2104 / RFC 4648), esta implementación no ha sido auditada formalmente y no debe emplearse en aplicaciones de seguridad crítica sin verificación independiente.
