Marcar al agua ensamblados .NET para rastrear quién filtró una build
La ofuscación impide que un lector entienda tu código; no hace nada por decirte qué cliente lo filtró. Una marca de agua por licenciatario sí — un marcador único y encubierto horneado en cada build que sobrevive a la copia casual y mapea un ensamblado filtrado de vuelta a la cuenta a la que se emitió. Aquí tienes qué debe ser una buena marca de agua, dónde vive, y cómo complementa a la ofuscación y al licenciamiento.
La ofuscación responde a la pregunta «¿puede alguien leer mi código?». No hace nada por una pregunta distinta y a menudo más dolorosa: «alguien filtró mi software — ¿quién?». Cuando una build que vendiste a un cliente aparece en un foro, un sitio de intercambio de archivos o la máquina de un competidor, la ofuscación no puede señalar de vuelta al origen. El marcado al agua sí. Una marca de agua es un marcador encubierto, por licenciatario, horneado en cada copia para que un ensamblado filtrado pueda rastrearse hasta la cuenta a la que se emitió. Es una herramienta distinta para un trabajo distinto, y se combina de forma natural tanto con la ofuscación como con el licenciamiento.
Atribución, no confidencialidad
Ayuda ser preciso sobre para qué sirve una marca de agua. La ofuscación eleva el coste de entender tu código. Una marca de agua eleva el coste de filtrarlo al hacer la filtración atribuible. Las dos son ortogonales: una build ofuscada sin marca de agua es ilegible pero anónima — una copia en un torrent no te dice nada sobre quién la puso ahí. Una build marcada al agua que no está ofuscada es perfectamente legible pero rastreable. Quieres ambas: difícil de entender y rastreable de vuelta al licenciatario si se escapa.
Ese replanteamiento importa porque cambia el criterio de éxito. La ofuscación «funciona» si un atacante se rinde al intentar leer el código. Una marca de agua «funciona» si, dada una copia filtrada, puedes decir de forma fiable esta build se emitió a la cuenta X. Nada de la marca de agua necesita impedir que la filtración sea usable — necesita hacer al filtrador identificable, lo que es un disuasivo en sí mismo una vez que los clientes saben que las builds están marcadas individualmente.
Qué debe ser una buena marca de agua
No toda «cadena oculta» es una marca de agua. Una utilizable tiene cuatro propiedades:
- Única por licenciatario. La build de cada cliente (o cada copia activada) lleva un marcador distinto — típicamente un identificador por cuenta, o un token opaco que mapea a uno en tus registros. Dos clientes nunca deben compartir un marcador, o se pierde la atribución.
- Encubierta. No debería ser una obvia cadena
CustomerId = "12345"sentada en los metadatos, que es lo primero que cualquiera encontraría y borraría. Un buen marcador no es visiblemente un marcador — se mezcla con estructuras que normalmente llevan ruido. - Robusta al manejo casual. Debe sobrevivir a las cosas corrientes que le pasan a un archivo en tránsito: copiar, comprimir, renombrar, mover entre máquinas. No necesita sobrevivir a un atacante determinado y consciente de la marca de agua (nada lo hace), pero no debe caerse por el simple hecho de ser pasada adelante.
- Verificable de forma determinista. Dada una copia sospechosa, debes poder extraer el marcador mecánicamente y cotejarlo con tus registros de emisión sin conjeturas. La atribución que depende de «parece la suya» no es atribución.
La tensión está entre encubierta y robusta: cuantos más lugares codifiques el marcador de forma redundante, más difícil es eliminarlo, pero más superficie hay para notarlo. La respuesta práctica es la redundancia en varias ubicaciones discretas, de modo que eliminar una copia no destruya la marca.
Dónde puede vivir la marca
Un ensamblado .NET tiene muchos lugares que toleran variación por build sin cambiar el comportamiento, que es exactamente donde pertenece una marca de agua. El punto no es ningún escondite único sino que el marcador esté entretejido en estructuras que normalmente varían de build a build, de modo que su presencia no sea conspicua:
// The marker is derived, not a plaintext ID — an opaque value that maps
// back to a licensee only through your issuance records.
// e.g. marker = HMAC(issuanceSecret, licenseeId) → a 128-bit token
La idea rectora es codificar ese token opaco en partes incidentales y neutrales en comportamiento de la build, en vez de en un campo que grite «identidad». Como el token es derivado y no un número de cuenta literal, incluso alguien que encuentre una copia de él no aprende nada sobre el esquema ni sobre otros clientes, y solo se resuelve a un licenciatario mediante registros que solo tú tienes. Un ofuscador bien diseñado puede llevar el marcador a través de sus transformaciones de modo que el marcado al agua y la protección ocurran en una sola pasada en vez de como un paso añadido que un atacante pueda diferenciar contra una build no marcada.
Verificar una filtración
El flujo de trabajo cuando aparece una copia sospechosa es mecánico, que es justo el punto. Pasas la extracción sobre el archivo, recuperas el token incrustado, y lo buscas en los registros de emisión que mapean tokens a cuentas. Como el token se derivó de forma determinista (por ejemplo como un HMAC sobre el id del licenciatario con un secreto que solo tú tienes), también puedes re-derivar el token esperado para cualquier cuenta y confirmar la coincidencia, en vez de confiar solo en una tabla de búsqueda. El resultado es una afirmación defendible — este binario lleva el marcador emitido a la cuenta X en la fecha Y — no una corazonada.
Aquí es también donde el marcado al agua y el licenciamiento se refuerzan mutuamente. Si cada activación ya vincula una copia a un licenciatario, la identidad de activación es una semilla natural para la marca de agua, y el marcador de una build filtrada se alinea con la misma cuenta que tus registros de licenciamiento ya rastrean. La protección, la activación y la atribución terminan contando una historia consistente sobre de dónde vino una build.
Límites honestos
Una marca de agua es un disuasivo y una herramienta de rastreo, no DRM. Tres límites vale la pena declararlos con claridad. Primero, no previene nada — una build filtrada aún corre; la marca de agua solo te dice de quién era. Segundo, no es irremovible: un atacante que sabe que la marca existe, sabe dónde vive y está dispuesto a gastar esfuerzo puede eliminarla o corromperla, por lo que la redundancia y el encubrimiento importan y por lo que no deberías sobrevenderla. Tercero, la atribución es solo tan fiable como tus registros de emisión y el secreto de tu clave de derivación; si esos se filtran, también lo hace el valor del esquema.
Dentro de esos límites se gana su lugar. La mayoría de las filtraciones no son operaciones sofisticadas de des-marcado — son un licenciatario entregando calladamente una build a alguien que no debería tenerla, a menudo sin darse cuenta de que la copia está marcada individualmente. Una marca de agua encubierta, redundante y verificable de forma determinista atrapa exactamente ese caso y, una vez que los clientes saben que las builds están marcadas, lo desalienta de entrada. Ofusca para que el código sea difícil de leer, marca al agua para que una filtración tenga un nombre, y licencia para que cada copia esté vinculada a una cuenta — tres capas, tres trabajos, una respuesta coherente a «qué le pasó a mi software después de que salió del edificio».
Prueba Nebula.NET
Endurece tu código .NET en minutos — empieza con la edición gratuita.