Qué queda en el servidor después de leer una vez un enlace de un solo uso

Alguien deja la contraseña de una base de datos en un chat, o pega un código de recuperación en un ticket. La pregunta que llega después casi nunca es «qué algoritmo». Es: cuando la otra persona lo abrió, ¿en qué máquina sigue esa cadena? Un enlace que se destruye al leerlo responde la segunda mitad. El servidor nunca tuvo texto en claro. El cifrado se borra tras una recuperación correcta que agota el cupo. La URL puede seguir en el historial del chat. Eso es otra capa de riesgo.

Quédate con una afirmación que puedes comprobar: la primera apertura de la página de lectura debe cargar solo la cáscara. Aún no pide el cifrado al servidor. Después de pulsar Abrir y ver, Red (Network) debería mostrar una petición por id. Tras una recuperación correcta que agota el cupo, el mismo id debería responder 410. La barra de direcciones puede conservar la clave detrás de #. Esa misma cadena no debería aparecer en la línea de petición.

«Se destruye al leerlo» se entiende a menudo como: el enlace se quema y el contenido desaparece. La metáfora suele apuntar al objeto equivocado. El texto azul del chat no se borra solo; el historial del navegador, el correo original y el comentario del ticket también se quedan. Lo que se borra es el cifrado temporal en el anfitrión. La URL completa en la mano de alguien sigue siendo una cadena de caracteres. Ábrela otra vez y el id apunta a un objeto que ya no está.

Separa dos credenciales antes de preguntar qué queda. Una es el localizador: el id detrás del interrogante, que le dice al servidor qué registro recuperar. La otra es la clave de descifrado: el fragmento detrás de la almohadilla, que se queda en esta pestaña. El anfitrión está diseñado para ver la primera y no ver la segunda. Tras una lectura correcta, el localizador puede seguir nombrando un hueco vacío. Si la clave sigue en el historial del chat, no descifra nada, porque el cifrado ya no está.

Un enlace de disco compartido funciona al revés. Mientras ese enlace vale, el archivo suele seguir en el disco. Revocas el acceso; no borras el objeto al abrirlo la primera vez. Un enlace de un solo uso ata «leer» y «borrar» a la misma recuperación. Al crear puedes fijar el número de lecturas (1–10, 1 por defecto) y la caducidad (1 hora, 24 horas, 7 días, o destruir solo al leer). El camino por defecto es: primera recuperación correcta, el cifrado se borra. Sin leer y caducado, el mismo borrado. Ninguna de las dos rutas deja una copia en claro que puedas recuperar después.

El servidor nunca guardó texto en claro

«Borrar el texto en claro después de la primera lectura» es la otra lectura errónea habitual. El orden es: el texto en claro existe solo en la pestaña de quien crea; el navegador extrae una clave aleatoria de 256 bits con Web Crypto y cifra con AES-256-GCM; los campos de salida son cifrado, caducidad y lecturas máximas. El servidor devuelve un id que no se puede adivinar. La página entonces añade la clave a s.html?id={id}#{key}. No hay un paso que escriba la contraseña en una base de datos y la cifre después.

Los parámetros se pueden contrastar; no son un eslogan. NIST SP 800-38D recomienda un IV de 96 bits (12 bytes) para GCM, para que las implementaciones sigan siendo interoperables y simples; la etiqueta de autenticación suele ser de 128 bits (16 bytes). AesGcmParams de Web Crypto encaja con esa forma. En MakePwd, el cifrado es Base64 de IV de 12 bytes + cifrado + etiqueta de 16 bytes. La clave tiene 32 bytes. Cada elemento tiene un tope de 32 KB. Esos números están en la página de creación. También puedes inspeccionar un enlace que acabas de generar: lo que va detrás de la almohadilla es la clave en Base64URL, no la frase que escribiste.

GCM también da integridad. Si el cifrado o la etiqueta se alteran, el descifrado falla en esta pestaña. No obtienes un texto en claro «casi igual». Eso bloquea la manipulación en tránsito. No bloquea una captura de pantalla después de que alguien ya haya leído el mensaje. El servidor nunca tiene la clave, así que no puede descifrar por ti, no puede revisar el cuerpo y no puede fabricar una copia en claro después de que leas. Lo que puede hacer es entregar el cifrado por id y luego borrarlo por cupo o por TTL.

Etapa En el navegador En el servidor
Al terminar de crear El texto en claro que acabas de escribir y el enlace completo Cifrado, id, TTL, lecturas restantes
La página de lectura se pinta Id y clave # en la barra de direcciones Sigue el cifrado; esta pintura no lo borra
Confirmas y la recuperación funciona Texto en claro descifrado en esta pestaña Borrado si el cupo se agota; si no, el cupo baja en uno
El mismo id se abre otra vez La clave puede seguir en la barra de direcciones Destruido o caducado; una respuesta de tipo 410

El id va en la consulta, la clave en la almohadilla

Un enlace de un solo uso tiene que hacer dos trabajos: decirle al servidor qué registro recuperar, y decirle al navegador del destinatario qué clave usar. Esos trabajos no deberían compartir un mismo campo HTTP. La consulta detrás de ? se escribe en la línea de petición, así que el proxy inverso y el registro de acceso están diseñados para verla. El fragmento detrás de # queda para el cliente.

La sección 7.1 de RFC 9110 es explícita: el URI de destino no incluye el fragmento, porque el identificador de fragmento lo procesa el cliente. El navegador le pide al servidor s.html?id=…, no la barra de direcciones entera. Si mueves la clave a ?key=, «solo se guarda cifrado» ya es falso en la capa de logs: quien abra el registro de acceso de ese día tiene la segunda mitad necesaria para descifrar.

La nota anterior ya recorrió esa capa. El límite aquí es corto: la separación evita que el anfitrión vea la clave. No hace que la cadena completa sea segura de reenviar. Pega una URL que aún contiene # en un chat y tanto el destinatario como el proveedor del chat pueden ver la clave. Un fragmento bloquea HTTP. No bloquea el portapapeles. Las comprobaciones que puedes hacer en Red están en Por qué el fragmento # de una URL encaja para una clave y cuándo esa protección falla.

El punto de destrucción es recuperar el cifrado, no la primera pintura

Abrir la página de lectura y consumir una lectura no son el mismo suceso. El HTML puede pintarse primero: comprobar que la dirección tiene ?id= y una clave #, y detenerse en un botón de confirmación. Ese paso solo gasta recursos estáticos. La petición que le dice al servidor que borre el cifrado es la recuperación posterior por id. Después de pulsar, si la recuperación funciona y el cupo se agota, el registro pasa de «aún se puede recuperar» a «destruido».

Si falta la clave, no debería pedirse el cifrado. Si la dirección solo tiene un id —porque el chat recortó la almohadilla— la página de lectura debería decir que falta el fragmento, no pedirle primero el cifrado al servidor. Si no, gastas la única lectura: el servidor entrega el cifrado y lo borra, el navegador no puede descifrar, y quien envió cree que la otra persona ya lo leyó. El comportamiento comprobable es: sin #, Red no debería mostrar la API de cifrado; con el enlace completo y un clic de confirmación, sí.

El cupo no es siempre 1. Al crear puedes fijar de 1 a 10. Si pones 3, tras las dos primeras recuperaciones correctas el cifrado sigue en el servidor; solo bajó el cupo restante. La tercera correcta lo borra. El 1 por defecto encaja con «pasar una contraseña», no con un protocolo que solo se pueda leer una vez. El TTL es otra condición de borrado independiente: sin leer tras 24 horas, el cifrado también desaparece, y el estado debería distinguirse de «alguien ya lo leyó». La página de lectura de MakePwd trata expired en una respuesta 410 como caducidad; el resto de estados destruidos se tratan como ya quemados.

No hagas la prueba con una contraseña real, una clave de API de producción o una cadena de conexión sin enmascarar. Usa un canario desechable, por ejemplo canary-burn-2026-do-not-reuse. Estás contrastando códigos de estado y la línea de petición, no repartiendo otro secreto.

410 y 404: destruido, caducado o nunca estuvo

Cuando el cifrado ya no está, el servidor sigue teniendo que responder a la siguiente petición. La sección 15.5.11 de RFC 9110 escribe 410 Gone así: el recurso de destino ya no está disponible en el servidor de origen y esa condición es probablemente permanente. Si el origen no puede decir si es permanente, debería usar 404. La nota de MDN sobre el 410 añade que los clientes no deberían insistir, y que el sitio debería retirar los enlaces que aún apuntan a ese recurso.

En un enlace de un solo uso, 410 encaja mejor que 404: este id alguna vez nombró un objeto cifrado, luego ese objeto se borró a propósito y no volverá. La caducidad y un cupo agotado pueden devolver ambos 410, con el cuerpo de la respuesta separando expired de una destrucción normal para que el destinatario no crea que escribió mal el enlace. 404 puede aparecer igual: un id inválido, o un registro limpiado tan a fondo que el anfitrión ya no declara que existió. Para quien envía, las dos respuestas significan lo mismo: no esperes que el anfitrión te devuelva el contenido.

El 410 también les dice a los rastreadores que dejen de venir. La página de lectura es un aterrizaje temporal de cifrado. Debería ser noindex para buscadores y no entrar en un sitemap. No quieres que se indexe una página de contraseña de un solo uso. La página de creación sí puede indexarse, porque explica cómo generar un enlace. La de lectura solo sirve a quien ya tiene la URL completa.

Por qué la vista previa de un chat puede gastar la única lectura

El accidente habitual no es que un criptógrafo rompa AES. Es que un robot de vista previa se adelanta al clic de un compañero. Pegas la URL completa en un chat. El cliente pide esa dirección para dibujar una tarjeta. La documentación de expansión de enlaces de Slack lo dice claro: por defecto, cuando aparece un enlace en un mensaje, Slack lo recupera y ofrece una vista previa. Teams, Discord y algunos escáneres de correo hacen una petición parecida. Quieren un título y un resumen, no tu contraseña. Si «el primer GET también recupera el cifrado y lo borra», ese rastreo gasta la única lectura. El compañero abre una página ya destruida.

Una clave detrás de # es una capa de protección frente a esa petición en el servidor: el robot pide s.html?id=… y el fragmento no se envía con HTTP. La vista previa suele no poder descifrar, así que la tarjeta no muestra la contraseña. Lo que puede gastar es el cupo de lecturas, no la clave en sí. Si tu página de lectura pide el cifrado al cargar, la vista previa equivale a pulsar Abrir y ver por el destinatario. Recibe un 410. Tú crees que ya lo leyó.

La solución practicable no es «nunca pegues un enlace». Es separar «pintar la página» de «recuperar el cifrado». La página de lectura pinta primero un paso de confirmación y dice que este clic gastará una lectura; solo entonces envía la petición. Una vista previa que toma el HTML y no pulsa el botón se detiene en la confirmación. Eso no detiene a un escáner que ejecuta el script completo y simula un clic, ni a una persona que pulsa la tarjeta equivocada. Cambia el fallo habitual de «la tarjeta lo abrió una vez» de resultado por defecto a enlace que sigue vivo.

Una segunda práctica ayuda en canales que expanden solos: envía «la contraseña va por un enlace de un solo uso; la URL en otro mensaje», o pon la cadena completa en un mensaje directo que no haga vista previa. La URL completa sigue siendo una credencial al portador. La vista previa solo gasta un cupo. No cambia «quien tenga la cadena entera puede descifrar».

Compruébalo en vivo: detente en la confirmación y busca en Red

«Se destruye al leerlo» y «conocimiento cero» no se demuestran solos. Cuatro cosas que puedes ver: si al crear salió texto en claro; si la primera carga de la página de lectura pidió el cifrado; si la línea de petición de la confirmación incluye la clave; y si una segunda apertura tras agotar el cupo es 410.

Prepara primero un canario. Abre la página de creación del enlace de un solo uso, escribe una frase desechable, pon el TTL en 1 hora y deja las lecturas en 1. No se lo envíes a nadie. Mira la separación en el panel de resultado: el lado de la consulta es solo el id; el lado del fragmento es la clave. Luego abre Red en las herramientas de desarrollo, activa Conservar registro y busca el canario completo. El cuerpo de la creación debería ser cifrado, no esa frase. Los analytics tampoco deberían llevarla.

  1. Abre la página de lectura con el mismo enlace completo. Aún no pulses confirmar. Red debería mostrar el documento y los recursos estáticos, no una recuperación de cifrado por id.
  2. Compara la barra de direcciones con la línea de petición del documento. La barra conserva # y lo que sigue. La línea de petición solo debería mostrar la ruta y ?id=.
  3. Pulsa confirmar. Entonces debería aparecer la recuperación del cifrado. Tras el éxito, la página muestra el texto en claro. Abre el mismo enlace una segunda vez. Deberías caer en destruido o 410, no en la misma frase canario.

Tres pases limpios solo sostienen una afirmación estrecha: en este navegador, en esta ejecución, el texto en claro no salió como campo de negocio observado, la clave no entró en la línea de petición y el cifrado ya no se pudo recuperar tras una lectura correcta. No demuestra que una extensión no leyó el cuadro de texto, ni que un disco comprometido nunca haya guardado cifrado histórico. Cambia de navegador y merece la pena repetir el canario. Si también necesitas comprobar si el texto en claro salió de alguna página de herramienta, los pasos están en Cifrar en el navegador: cómo comprobar que el original no salió de este dispositivo.

No impide copiar, hacer capturas ni reenviar la URL completa

Un enlace que se destruye al leerlo reduce dos riesgos: texto en claro guardado a largo plazo en el servidor, y el mismo cifrado abierto una y otra vez. No reduce un tercero: qué hace el destinatario cuando ya puede ver el texto en claro. Puede copiar, hacer una captura, reenviar o leerlo en voz alta. El enlace mismo se puede reenviar entero —clave e id viajan juntos— y la siguiente persona puede descifrar en la página de lectura mientras quede una lectura.

Por eso «el destinatario debe iniciar sesión» no resuelve el traspaso. El control de acceso es la URL completa. Poner una cuenta en la página de lectura no impide que el servidor vea la clave: la clave no debería enviarse al servidor. Solo añade una puerta al destinatario y cambia «quién tiene el enlace» por «quién tiene una cuenta». MakePwd no tiene cuentas ni almacén de contraseñas. Crear y leer se abren sin registro. La página de lectura es pública para el destinatario.

La capacidad también tiene un límite. 32 KB bastan para una contraseña, una clave de API, un código de recuperación y una nota corta. No bastan para un volcado de base de datos o un paquete entero de certificados. Un archivo completo debería convertirse en .lock / .enc en este dispositivo y viajar por un disco o por correo. Cifrar archivo admite un archivo de hasta 5 GB, también sin cuenta, y no sube el texto en claro por defecto. Meter un archivo grande en un canal de texto de un solo uso no es «más seguro». Está fuera de lo que este camino está pensado para hacer.

Practica el estado destruido en una página de creación que se abre sin cuenta

Si quieres una herramienta que escriba la separación consulta / fragmento en el panel de resultado, empieza por el enlace de un solo uso de MakePwd. Se abre sin registro en ambos lados. Al crear, el cifrado ocurre en esta pestaña con AES-256-GCM. El servidor recibe cifrado, ttl_hours (0–168, 24 por defecto) y max_reads (1–10, 1 por defecto). El enlace generado es siempre s.html?id={id}#{key}. La página de lectura se detiene en la confirmación, luego recupera el cifrado y descifra en esta pestaña.

Usa el canario de arriba y deja las lecturas en 1. Mira tres sitios a la vez: la separación consulta / fragmento al crear, si Red pide el cifrado en la primera carga de la página de lectura, y si la segunda apertura está destruida. La separación confirma que la clave no entró en la consulta. La primera carga confirma que una vista previa no quemará por accidente. La segunda apertura confirma que «se destruye al leerlo» no es solo un texto.

Si el contenido aún necesita quitar parámetros de seguimiento antes de enviar cualquier otra cosa, pasa primero por limpiar enlaces y luego mete la cadena corta que de verdad tiene que seguir en secreto en el enlace de un solo uso. La limpieza quita UTM e IDs de clic de una consulta. No sustituye un canal de cifrado de un solo uso. Ninguno de esos pasos exige una cuenta, y ninguno muestra un buzón de soporte que no está conectado.

Preguntas frecuentes

Después de abrir una vez, ¿queda texto en claro en el servidor?

El texto en claro nunca se guardó. El navegador cifra primero. El servidor solo recibe cifrado. Tras una recuperación correcta que agota el cupo, el cifrado se borra. Una petición posterior al mismo id debería ver 410 o el estado de destruido. La cadena de la URL puede seguir en el historial del chat. Eso no es una copia en claro en el anfitrión.

¿La vista previa de un chat destruye el secreto primero?

Puede gastar el cupo. Suele no poder quemar el texto en claro. La vista previa pide la página; la clave detrás de # no entra en HTTP. Si la página de lectura pide el cifrado al cargar, la vista previa gasta la única lectura. La implementación correcta confirma primero y luego recupera. Slack y productos similares piden por defecto los enlaces de un mensaje para dibujar una tarjeta.

¿Qué diferencia hay entre 410 y 404?

410 significa que el recurso estuvo disponible y se ha quitado de forma permanente; los clientes no deberían reintentar. 404 es más vago: el id puede ser inválido, o el registro ya se ha limpiado. Un enlace de un solo uso queda más claro cuando 410 separa destruido de caducado. En ambos casos no hay texto en claro que restaurar.

¿Hace falta iniciar sesión para abrir la página de lectura?

No. Crear y leer se abren sin cuenta. El destinatario pide el cifrado con el id y descifra en esta pestaña con la clave del fragmento. MakePwd no tiene cuentas ni almacén de contraseñas. El control de acceso es el enlace completo.

Tres cosas que recordar la próxima vez que envíes un secreto de un solo uso

Primero, cuando preguntes «qué queda después de una lectura», pregunta si el texto en claro estuvo alguna vez en el servidor. No estuvo. Lo que se borra es el cifrado. Una URL que sigue en el chat no descifra nada. Segundo, el punto de destrucción es la recuperación del cifrado, no la primera pintura de la página de lectura. El botón de confirmación está para los robots de vista previa, no para quien ya tiene la cadena entera. Tercero, mira Red: busca el canario al crear, compara la línea de petición con # al leer, y busca 410 en la segunda apertura.

Si aún quieres el motivo por el que una clave puede ir detrás de la almohadilla, lee Por qué el fragmento # de una URL encaja para una clave y cuándo esa protección falla. Esta nota solo convierte «qué queda en el servidor después de una lectura» en un rango que puedes contrastar con códigos de estado y el momento de las peticiones.