¡Hoy estamos en Product Hunt! Dejar un comentario

Ya está disponible Authenticator 1.16.0. Ver las novedades

Cómo funciona

Un resumen técnico para quien audita la seguridad. Todo lo que hay aquí se puede contrastar con el código fuente, incluido lo que nos deja peor: lo decimos en lugar de omitirlo.

Arquitectura

La extensión es una extensión de Chrome con Manifest V3 que genera códigos TOTP localmente con OTPAuth. Generar códigos, guardar las cuentas y cifrarlas no implica ningún backend, y no hay cuentas de usuario. Operamos un único servidor, api.authenticator.sh, para el plan gratuito y Pro —descrito más abajo—, y nunca recibe un secreto, un nombre de cuenta, un emisor ni un código.

Ese servidor no guarda material de clave de las cuentas de nadie: no hay depósito de claves ni ninguna vía de recuperación bajo nuestro control. Si alguien olvida su contraseña y pierde su código de recuperación, no podemos ayudarle: es intencionado.

Criptografía

La protección por contraseña es opcional y está desactivada por defecto. Con ella activada:

Cifrado
AES-256-GCM, IV aleatorio de 96 bits por registro y por reescritura
Derivación de clave
PBKDF2-HMAC-SHA256, 600.000 iteraciones, sal aleatoria de 128 bits (recomendación de OWASP)
Clave maestra
256 bits, de crypto.getRandomValues, generada una sola vez; la contraseña solo la envuelve
Huellas
HMAC-SHA256 con una subclave derivada por HKDF, usada para fusionar registros entre dispositivos
Almacenamiento de la clave desbloqueada
chrome.storage.session — solo en memoria, nunca se escribe en disco, se borra al cerrar el navegador e inalcanzable desde scripts de contenido
Bloqueo automático
En cada apertura / a los 5, 15 o 60 minutos de inactividad / hasta que se cierre el navegador
Implementación
Solo WebCrypto. Ninguna primitiva está escrita a mano y no se empaqueta ninguna biblioteca criptográfica.

Por qué hay dos niveles de clave

Los datos nunca se cifran directamente con una clave derivada de la contraseña. Una clave maestra aleatoria cifra los registros; la salida de PBKDF2 solo envuelve esa clave maestra. De ahí se siguen dos consecuencias, y ambas importan:

  • Cambiar la contraseña reescribe 32 bytes en lugar de volver a cifrar cada registro, cada copia de seguridad y cada exportación. Esa reescritura masiva es justo donde los sistemas de almacenamiento pierden datos.
  • Un código de recuperación de 160 bits puede envolver la misma clave maestra de forma independiente, así que una contraseña olvidada tiene remedio. El código se muestra una vez, hay que volver a escribirlo para confirmarlo y se renueva tras cada uso. No se escribe nada en disco hasta que el usuario lo ha confirmado.

Qué se cifra y qué no

Con la protección por contraseña activada se cifra el registro completo de la cuenta salvo su identificador y su huella —incluido el nombre del servicio—, de modo que un perfil robado no revela en qué servicios tiene cuentas el usuario. Al activarla, la extensión cifra los registros, los verifica descifrándolos y comparándolos, y solo entonces elimina todas las copias en claro: almacenamiento local del navegador, sincronización y las siete instantáneas rotativas de respaldo.

Lo decimos sin rodeos, porque afecta a su evaluación:

  • Con la protección por contraseña desactivada, las cuentas se guardan sin cifrar y, si Chrome Sync está activo, se replican a través de la propia cuenta de Google del usuario. Nosotros nunca las recibimos, pero Google las tiene. La sincronización puede desactivarse, lo que además elimina lo que ya haya allí.
  • El historial de uso por sitio —qué cuenta se usa en qué dominio— se guarda localmente y no queda cubierto por la bóveda mientras se recopila. Activar la protección por contraseña lo borra; también puede desactivarse por separado.

Modelo de amenazas

Protege frente a

  • Un directorio de perfil del navegador robado o copiado
  • Malware infostealer que exfiltra datos del navegador en bloque
  • Alguien con acceso físico a un equipo desatendido
  • Registros de cuentas que llegan a Google mediante Chrome Sync
  • La pérdida o el robo de un archivo de copia de seguridad (exportación protegida con contraseña)

No protege frente a

  • Malware ejecutándose como el usuario mientras la bóveda está desbloqueada
  • Un keylogger que capture la contraseña mientras se teclea
  • Un navegador o un sistema operativo comprometidos
  • El compromiso de los servicios de terceros para los que son los códigos
  • Una exportación sin cifrar que decidiste escribir: JSON simple, enlaces otpauth:// o CXF son legibles por cualquiera que abra el archivo

En resumen: esto protege los datos en reposo. No defiende, ni puede defender, un dispositivo que ya está bajo el control de un atacante mientras se usa.

Permisos

El manifiesto declara cinco permisos y ningún permiso de host:

storage
Cuentas y ajustes en el almacenamiento local, la clave desbloqueada en el de sesión y la copia opcional de sincronización
activeTab
Lee el nombre de host de la pestaña activa para destacar la cuenta correspondiente, y captura la pestaña cuando el usuario escanea un código QR desde la pantalla
contextMenus
Añade la entrada «Insertar código 2FA» al menú contextual, y solo en campos editables de páginas http(s)
scripting
Inyecta la única función que pone el código en el campo — para una sola pestaña y una sola invocación, solo después de elegir esa entrada del menú
sidePanel
Abre la aplicación en el panel lateral de Chrome cuando es la superficie elegida; no concede acceso a ninguna página

No hay No hay scripts de contenido, ni permisos tabs, cookies o webRequest, ni web-accessible resources. Nada nuestro existe en una página hasta que pides un código allí; lo que se ejecuta entonces se inyecta para esa pestaña y esa invocación, bajo el permiso que concede tu propio clic, y no deja nada detrás.

Cámara

El escaneo de QR con la cámara se ejecuta en una página de la extensión abierta en una pestaña, no en el popup: un popup se destruye al perder el foco, que es justo lo que provoca una solicitud de permiso. Para esto no hace falta ningún permiso en el manifiesto: usa la solicitud de cámara estándar del navegador, concedida por origen de la extensión y revocable en la configuración del sitio, y nunca se pide hasta que el usuario abre el escáner. El vídeo se decodifica localmente y nunca se transmite ni se guarda.

Plan gratuito, Pro y la consulta diaria

Desde la 1.14.0, la extensión consulta api.authenticator.sh una vez al día: al iniciarse el navegador o después de que se haya mostrado el popup, nunca de camino a mostrar un código. La petición es una lista cerrada de campos, construida en una sola función en src/billing/sync.ts y ceñida a esa lista por una prueba:

iid
Un identificador aleatorio creado por esta instalación. Sin vínculo con una cuenta de Google, una dirección de correo ni el navegador — salvo si el usuario compra Pro: entonces el registro de la compra vincula la instalación desde la que compró, y cualquier instalación en la que la clave de licencia esté activa, a la dirección de correo con la que pagó
ver, ui_lang, browser_lang
Versión de la extensión, idioma de la interfaz, idioma del navegador
installed_at, legacy, legacy_src
Cuándo apareció la instalación y si es anterior a la facturación
accounts, groups
Cuántas cuentas contiene y en cuántos grupos: números, nunca cuáles ni cómo se llaman los grupos
vault, sync, open_mode, view_mode, style, dark, os
Si la protección por contraseña y la sincronización están activadas, y cómo se abre la extensión, cómo se muestra la lista de cuentas (normal, compacta o con los códigos ocultos), y qué estilo usa, claro u oscuro, y el sistema operativo (Windows, macOS, Linux, ChromeOS)
pid, lid, has_key, key_hint
Qué política y qué licencia firmadas tiene la instalación en ese momento, si ya tiene la clave de licencia y los últimos cuatro caracteres de esa clave
opens
Cuántas veces se abrió la extensión cada día desde la última consulta (como mucho, los últimos 14 días): un recuento por día, nada sobre lo que se miró
events
Eventos de facturación en cola, de una lista cerrada: se alcanzó el límite, se mostró o se cerró el paywall, se eligió un plan, se intentó un pago o una clave

Nunca se envían: secretos, nombres de cuenta, emisores, códigos ni los sitios en los que se usan. El servidor obtiene el país de la petición en el edge y no guarda la dirección IP. La página de desinstalación recibe el mismo identificador de instalación como ?i=: avisa al servidor de que la instalación se ha eliminado y vincula a ella una respuesta opcional a la encuesta.

Para esto se guarda en chrome.storage.local: el identificador de instalación, la última política y la última licencia firmadas, la clave de licencia que tiene esta instalación (escrita por el usuario o recibida tras comprar en ella), los recuentos diarios de aperturas hasta que el servidor los reciba y los eventos en cola; ningún dato de cuentas. Con la sincronización activada, también van a Chrome Sync el dato de si la instalación es anterior a la facturación y esa clave de licencia, para que los otros perfiles de Chrome del usuario los reciban y activen Pro solos; al desactivar la sincronización se quitan ambos de Chrome Sync, y al volver a activarla se vuelven a poner.

Qué se recibe de vuelta y cómo lo trata la extensión:

Tokens
La política (el límite de cuentas del plan gratuito) y la licencia (Pro) son JWS firmados con ES256 (ECDSA P-256)
Verificación
WebCrypto, con las claves públicas compiladas en la extensión (src/billing/keys.ts). Una compilación para la Chrome Web Store solo confía en la clave de producción, y la comprobación de compilación rechaza cualquiera que incluya otra
Sin conexión
El límite y la licencia se comprueban localmente, a partir de los últimos tokens firmados. Hasta que se recibe una política firmada no hay ningún límite: un servidor inaccesible nunca puede dejar a nadie fuera
Qué puede hacer el límite
Impedir que se añada una cuenta, y nada más. No puede ocultar, bloquear, eliminar ni modificar una cuenta, ni bloquear los códigos, la exportación, las copias de seguridad, la restauración o la sincronización
Compras
Stripe Checkout en una pestaña nueva, con Link como vendedor (merchant of record). El correo del comprador nos llega desde Stripe, para emitir y recuperar la clave de licencia; las claves se guardan como hash, junto con una copia cifrada para poder reenviar una clave perdida
Clave de licencia
La respuesta de la consulta diaria lleva la propia clave solo a la instalación en la que se compró Pro, y solo hasta que los cuatro caracteres que informa en key_hint coinciden con esa clave, de modo que una clave comprada después sustituye a una anterior; a una instalación en la que se escribió la clave nunca se le envía

El límite no es DRM: el código es abierto y un fork puede quitarlo. Lo que nunca debe ser posible es que alguien obtenga o use la licencia de otra persona, o que consiga limitar la instalación de otro: eso es una vulnerabilidad y entra en el alcance de nuestra política de seguridad.

Tráfico de red saliente

Hay dos tipos de petición que la extensión hace por su cuenta: la comprobación del reloj y la consulta diaria de facturación. Todo lo demás ocurre porque el usuario ha hecho clic en algo. Manifest V3 prohíbe el código remoto, y nada de esto lleva cuentas, claves secretas ni códigos generados. La lista completa:

HostCuándo¿Lleva datos del usuario?
time.akamai.com
timeapi.io
cloudflare.com
Comprobación de desfase del reloj, cacheada; si no hay ninguna fuente disponible, Ajustes lo indica. Con el reloj desfasado, TOTP deja de funcionar.No
authenticator.shPágina de bienvenida al instalar; página de opiniones al desinstalar, con el identificador de instalación en su dirección; páginas de ayuda y soporte cuando se abren desde la ventana emergente.El identificador de instalación, solo en la página de desinstalación; por lo demás, no (registros normales de servidor web)
api.authenticator.shLa consulta de facturación, una vez al día; llamadas de pago, licencia y portal cuando el usuario compra Pro, introduce una clave, restaura una compra o gestiona una suscripción.Los campos enumerados arriba, nunca datos de cuentas. Una clave de licencia o una dirección de correo, solo cuando el usuario la escribe
checkout.stripe.com
billing.stripe.com
Stripe Checkout y el portal de clientes, abiertos en una pestaña cuando el usuario compra Pro o gestiona una suscripción.Lo que el comprador introduzca allí. Se aplican las condiciones de Stripe y de Link
chromewebstore.google.comFicha de la tienda, abierta en una pestaña al hacer clic: el formulario de reseña desde el aviso de valoración.No (Google ve la visita a una página de la tienda como cualquier otra)

No se cargan fuentes, scripts, estilos ni imágenes desde hosts de terceros. Todo lo necesario para representar la interfaz viaja dentro del paquete.

Verificar lo que publicamos

No hace falta creerse nada de lo anterior. La extensión es de código abierto y cada versión es reproducible:

  1. Descargue el .crx publicado desde la Chrome Web Store y descomprímalo
  2. Sitúese en la etiqueta de git correspondiente
  3. Ejecute npm ci && npm run build con Node 20 LTS
  4. Compare el dist/ resultante con el paquete descomprimido

Con cada publicación en GitHub se publica un SHA-256 de cada archivo del dist/ generado, como SHA256SUMS-v<version>.txt y en formato sha256sum, de modo que el paso 4 se reduce a una sola ejecución de sha256sum -c. Las diferencias deberían limitarse al orden de los archivos dentro de los archivos comprimidos y a los espacios en la salida minificada entre versiones de parche de Node.

Las dependencias en tiempo de ejecución son deliberadamente pocas —OTPAuth, React, jsQR, Zustand, Framer Motion y Lucide— fijadas por un lockfile versionado. Facilitamos un SBOM en formato CycloneDX si se solicita.

Ver el código fuente

Lo que no tenemos

Somos un proveedor pequeño e independiente. Si alguno de estos puntos es un requisito ineludible en su proceso, mejor saberlo ahora que tres semanas después de empezar la evaluación:

  • Sin certificación SOC 2, ISO 27001 ni equivalente
  • Ninguna prueba de intrusión externa encargada hasta la fecha
  • Sin programa remunerado de bug bounty

Lo que ofrecemos a cambio es transparencia total del código, una compilación verificable, una superficie mínima de permisos y un servidor al que, de entrada, nunca se le envían sus secretos, nombres de cuenta ni códigos.

Cómo contactarnos

Informes de vulnerabilidades y preguntas de seguridad: security@authenticator.sh. Nuestra política de divulgación, su alcance, los plazos de respuesta y las condiciones de safe harbour están en la página de política de seguridad; qué se almacena y qué sale del dispositivo se detalla en la política de privacidad.

Respondemos con gusto a un cuestionario escrito de evaluación de proveedores o revisamos el código con su equipo de seguridad.

Para cualquier asunto que no sea de seguridad —instalación, recuperación de cuentas, dudas de facturación en un despliegue— consulte soporte. Por favor, no envíe ahí informes de vulnerabilidades: ese buzón no se trata como confidencial.