counted

← Todas las guías

Cómo cifra Counted unas cuentas compartidas cuando el enlace es la clave

Cada proyecto de Counted tiene una clave aleatoria de 32 bytes que vive en el fragmento de la URL, que los navegadores nunca envían a un servidor. Todo lo que escribe un usuario se cifra en el dispositivo con XChaCha20-Poly1305 antes de salir. Las cuentas son opcionales; cuando hay una, la contraseña tampoco sale nunca del dispositivo. El cliente es AGPL y se compila de forma reproducible, así que cualquiera puede comprobar que lo que sirve counted.fr es lo que dice el código fuente.

Actualizado el 2 de noviembre de 2026

El enlace es la clave

Al crear un proyecto se genera en el dispositivo una clave aleatoria de 32 bytes. El enlace para compartir la lleva, codificada en base64url, después del #:

https://counted.fr/projects/<uuid>#<key>

Según la especificación HTTP, el fragmento se queda en el cliente: no se envía en la petición, así que nunca aparece en los registros del servidor ni en una cabecera Referer. Al abrirlo, el cliente guarda la clave localmente y quita el fragmento de la barra de direcciones. Ese es todo el modelo de acceso para quien no tiene cuenta: quien tiene el enlace puede leer y editar el proyecto. Trátalo como la llave de casa.

Qué se cifra, y cómo

Cada registro se serializa en JSON y se cifra como un único blob con XChaCha20-Poly1305, con un nonce aleatorio nuevo de 24 bytes en cada llamada. La etiqueta Poly1305 de 16 bytes hace que una clave errónea o un byte alterado haga fallar el descifrado de inmediato.

RegistroCampos cifrados
Proyectonombre, moneda, descripción
Participantenombre
Gastonombre, importe, fecha, tipo, descripción, categoría, moneda original y tipo de cambio
Línea de pagoimporte, pagado o debido
Regla recurrenteplantilla, calendario, cursor
Cuentanombre visible, preferencias, datos de pago

Por qué no AES-256-GCM: WebAssembly no tiene AES-NI, así que ChaCha20 es a la vez más rápido y de tiempo constante en software, y el nonce de 24 bytes hace que las colisiones de nonces aleatorios no sean un problema. La criptografía se ejecuta en Rust (el crate chacha20poly1305), compilado a WebAssembly para la web y de forma nativa para las apps de iOS y Android. Los saldos, la liquidación y los gráficos también se calculan en el dispositivo: el servidor nunca necesita un importe.

Cuentas opcionales, y lo que cuesta la custodia de claves

Una clave que vive en un solo dispositivo está a un teléfono perdido de desaparecer. Por eso, para quien decide crear una cuenta, cada clave de proyecto también se guarda en el servidor, envuelta bajo una clave de cuenta aleatoria, a su vez envuelta bajo una clave derivada en el dispositivo con Argon2id (64 MiB, t=3) a partir de la contraseña y una sal por cuenta. Un cambio de contraseña solo vuelve a envolver esa clave de cuenta; una cuenta que nunca ha cambiado su contraseña ni creado una clave de recuperación sigue usando directamente la clave derivada. Cuando un admin cambia la clave de un proyecto, la nueva llega a cada miembro con cuenta en una caja híbrida X25519 + ML-KEM-768; los demás reciben un enlace nuevo. El servidor guarda esos bytes y no puede leerlos.

Es una concesión deliberada. Sin custodia, quien tuviera la base de datos y la contraseña de un usuario no obtenía nada de sus proyectos. Con custodia, obtiene todos los proyectos de esa cuenta. La alternativa era no poder recuperar nada, lo que es peor para la mayoría. Los miembros anónimos no tienen custodia: sus únicas copias son el enlace y su dispositivo.

La contraseña nunca sale del dispositivo

Al iniciar sesión no se envía la contraseña. El cliente envía una prueba, un segundo Argon2id de la contraseña con una sal de inicio de sesión distinta, y el servidor solo guarda SHA-256(proof), comparado en tiempo constante. Nada de lo que el servidor recibe o guarda permite derivar la clave de la cuenta, y el servidor no realiza ninguna derivación de claves propia. El precio es un viaje de ida y vuelta extra para obtener la sal de inicio de sesión.

También significa que no podemos restablecer una contraseña por ti. Una clave de recuperación de 256 bits, que se muestra una sola vez y que el servidor nunca ve (solo guarda un hash derivado de ella), abre un segundo envoltorio de la clave de cuenta. Sin ella, un restablecimiento por correo surte efecto tras 72 horas, y una contraseña vigente desde hace 72 horas puede cancelarlo; la cuenta vuelve a empezar con una clave nueva, los proyectos vuelven desde los dispositivos y los enlaces, y la generación anterior se conserva 30 días y solo se abre con la contraseña antigua o la clave de recuperación.

Lo que el servidor aún ve

Decir solo «conocimiento cero» sería deshonesto. El servidor ve:

  • Para las cuentas: el correo, un hash de la prueba de inicio de sesión, las sales de derivación de claves, qué cuenta está en qué proyecto, los vínculos de amistad entre cuentas, hashes de los correos invitados con un secreto del servidor como clave (HMAC-SHA256), claves públicas, si la cuenta tiene una clave de recuperación y desde cuándo, cuándo cambió la contraseña y cualquier restablecimiento, quién administra cada proyecto, cada cambio de clave de un proyecto, quién lo hizo y a quién se quitó, y, si las notificaciones están activadas, el token push y su idioma.
  • Para todos: dirección IP, user agent y hora en registros que se guardan como máximo 30 días, el tamaño y el momento de las escrituras cifradas, y recuentos diarios de tablas completas.
  • La forma de cada cuenta compartida, solo en números: cuántos participantes y gastos, cuándo se añadió, editó o borró cada gasto y, para una cuenta que eligió quién es en un proyecto, qué participante eligió. Desde la versión 3.2.5 también se cifran quién participa en un gasto, quién lo introdujo y si el proyecto está cerrado. Un proyecto guardado por última vez con una versión anterior conserva esos vínculos en el servidor hasta que uno de sus miembros lo abre con una app actualizada, que los pasa a los datos cifrados; las copias de seguridad los conservan hasta 30 días más.
  • Una importación de Tricount: el navegador no puede llamar a Tricount directamente, así que el servidor obtiene el registro compartido por él y lo pasa en claro. No se guarda ni se registra; el dispositivo lo cifra antes de guardarlo en Counted.
  • La longitud del texto cifrado: un cifrado de flujo conserva la longitud del texto en claro, así que el servidor puede distinguir un gasto con descripción, o uno introducido en moneda extranjera, de uno sin ella. Nunca el contenido.

Nunca ve nombres, importes, fechas, descripciones ni nombres de proyecto. Cada columna en claro está listada y justificada en las notas de diseño, y una comprobación de versión falla si el esquema gana una que no lo esté.

Puedes comprobar el código

El cliente, todo lo que alguna vez ve texto en claro, está publicado bajo la AGPL en github.com/counted-labs/counted. La compilación es reproducible: la CI la rehace desde cero, compara el SHA-256 de cada archivo y firma la lista con Sigstore. counted.fr/es/verify muestra las huellas de lo que se sirve y cómo recompilarlo tú mismo.

El servidor es de código cerrado, para proteger las funciones de pago para empresas previstas ahí. Solo guarda texto cifrado, así que esa decisión no cambia nada en la confidencialidad de unas cuentas.

Límites conocidos

  • El cifrado de extremo a extremo en la web sigue confiando en la página que cargas. /verify demuestra que una versión coincide con su código fuente, y un monitor público comprueba cada seis horas, desde GitHub, que counted.fr sirve una versión firmada y no ejecuta otro código. Ninguno de los dos puede demostrar que la página que acabas de cargar sea esa versión: una sustitución servida a un solo usuario pasaría desapercibida. Las apps de iOS y Android traen su código al instalarse, así que una sola petición no puede sustituirlo.
  • Aún no hay auditoría independiente. Se han documentado siete rondas de auditoría interna, con sus correcciones.
  • Un enlace filtrado filtra el proyecto hasta que un admin cambia su clave. Una persona a la que se quita conserva lo que ya había leído o guardado, y un proyecto creado sin cuenta solo tiene admin si quien lo creó inicia sesión en el dispositivo en el que lo creó; los más antiguos no tienen, y para excluir a alguien de ellos hay que crear un proyecto nuevo.

Míralo en unas cuentas reales

Abre la demo con la pestaña de red de tu navegador: cada escritura es texto cifrado.

Sigue leyendo

Hacemos Counted. ¿Has visto un error en esta guía? Avísanos.