Observed since 20 Oct, 19:12 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
2fa-wasm
Biblioteca de autenticación de dos factores (2FA / HOTP / TOTP / Base32 / HMAC) de alto rendimiento en AssemblyScript / WebAssembly con TypeScript wrapper.
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).
memory.data + save/restore) y contextos hash como struct sobre puntero (changetype, sin new en el heap) — WASM Δ 0 B verificado bajo 500 000 operaciones.node:crypto (36/36 tests).otpauth://: toString() genera URIs escaneables y URI.parse() las reconstruye — integración directa con Google Authenticator, Authy, 1Password, etc.crypto.getRandomValues y conversiones Base32/hex.npm install 2fa-wasm
O con Bun / Yarn / pnpm:
bun add 2fa-wasm
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).
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" }).
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 2
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)
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 bytes
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: "usuario@ejemplo.com",
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().
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")
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)
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) |
memory.data), con save()/restore() por operación — ninguna reserva toca el heap del runtime.changetype (sin new), eliminando por completo las alocaciones en el heap.hotp_validate_raw compara el token generado con el recibido mediante XOR + OR acumulativo de longitud fija (sin early-exit), evitando fugas por timing.WASM Δ 0 B y heap estable bajo 500 000 operaciones.# 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
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
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.
Download counts and popularity will appear here as the collector receives them.
Declarations describe requirements; they are not a resolved dependency graph.
Source distribution
{
"shasum": "563ebf2e4a1761b72bb622b4b45724ad0ecedf94",
"tarball": "https://registry.npmjs.org/2fa-wasm/-/2fa-wasm-1.0.0.tgz",
"fileCount": 6,
"integrity": "sha512-sKzjRtA7NdRcwCs58efmGjnoCndGfiGrgAf0GDumPgnKrJ3l4QY+/oBwFUZ1gAPZzFheo4dD6DQqtorHJbhaOw==",
"signatures": [
{
"sig": "MEUCIA6DKjced/vTlC9Ul3/WoF9+EMia2QF4tYMBrZEPHlzuAiEA8dRAwSYLrzCwwNCqHrXP/mN6s0gvVxJ6GX3BiRig46c=",
"keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"
}
],
"unpackedSize": 44657
}Observed since 20 Oct, 19:12 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:afc55e73bef2706e905e2bf65530a577e5cfaa4b2b560a0c78bf8a88d1aed9dc