Skip to Content
Nueva versión 12 disponible 🎉
LicensingCifrado de código vinculado a la licencia

Cifrado de código vinculado a la licencia

Haga que una función protegida dependa criptográficamente de una licencia válida guardando la contraseña de cifrado de código dentro de la propia licencia.

Una comprobación de licencia booleana, if (!IsLicensed) return;, es fácil de eliminar con un parche. Este ejemplo muestra un patrón más sólido: el método sensible se cifra con el Cifrado de código de Babel Obfuscator, y la contraseña que la máquina virtual de Babel (BVM) necesita para ejecutarlo no está en absoluto en el binario. Está, a su vez cifrada, dentro de una licencia de Babel firmada y vinculada al hardware. Quitar la comprobación de licencia no basta: sin una licencia válida no hay contraseña, y sin la contraseña el método cifrado no se puede reconstruir ni ejecutar.

Ejemplo de código

git clone https://github.com/babelfornet/license-bound-encryption-console-example.git

El proyecto es una aplicación de consola .NET multiplataforma con dos partes: SecureApp, la aplicación con licencia cuyo método sensible tiene el código cifrado, y LicenseGenerator, una herramienta del lado del proveedor de software que emite licencias firmadas. La versión del paquete de Babel se centraliza en Directory.Build.props, de modo que el ejemplo puede usar la versión que corresponda a su fuente.

El método protegido

La propiedad intelectual que se protege es un único método. El atributo [Obfuscation] indica a Babel que cifre su cuerpo y lo guarde dentro del ensamblado (internal=true) con el nombre de origen core, usando una contraseña de tiempo de compilación:

[Obfuscation(Feature = "msil encryption:internal=true;source=core;password=C0re-Pr0tect!on-K3y", Exclude = false)] public static long Compute(int a, int b) { long acc = 0; for (int i = 1; i <= b; i++) acc += (long)a * i; return acc + 42; }

El cifrado de código se activa solo en la compilación Release, limitado a la clase sensible mediante una propiedad de MSBuild en SecureApp.csproj:

<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <MsilEncryption Condition="'$(MsilEncryption)' == ''">SecureApp\.Secret::.*</MsilEncryption> <BabelWarningsAsErrors>W00000</BabelWarningsAsErrors> </PropertyGroup>

El cifrado de código solo se ejecuta en la configuración Release. La línea BabelWarningsAsErrors convierte la advertencia del modo de evaluación W00000 en un error, de modo que un ensamblado sin proteger nunca se pueda distribuir por accidente.

Dónde está la contraseña

En tiempo de compilación el método se cifra con la contraseña C0re-Pr0tect!on-K3y. En tiempo de ejecución, la BVM pide esa contraseña a la aplicación mediante un hook get password antes de poder ejecutar el método. En lugar de devolver un valor escrito en el código, el hook valida la licencia y lee de ella la contraseña:

[Obfuscation(Feature = "msil encryption get password")] internal static string GetSourcePassword(string source) { var license = Validate(); // The code password is carried by the license as an encrypted field. var field = license.Fields.FirstOrDefault(f => f.Name == source) ?? throw new InvalidOperationException($"License does not grant source '{source}'."); // Decrypt the field with the vendor secret to recover the real code password. return field.Value.Decrypt(Secrets.FieldSecret); }

Validate() usa la clave pública RSA incrustada para verificar la firma de la licencia y aplica todas las restricciones, incluida la vinculación al hardware:

public static ILicense Validate() { var manager = new StringLicenseManager { SignatureProvider = RSASignature.FromKeys(PublicKey) }; return manager.Validate(File.ReadAllText(LicenseFilePath), typeof(Program)); }

Emitir la licencia

LicenseGenerator, del lado del proveedor de software, firma una licencia que se vincula al ensamblado SecureApp y al equipo actual, y que lleva la contraseña del código como campo cifrado. Por tanto, la contraseña nunca aparece sin cifrar, ni en el binario ni tampoco en el archivo de licencia:

string encryptedPassword = Secrets.CodePassword.Encrypt(Secrets.FieldSecret); var license = new StringLicense() .ForAssembly(assemblyFullName) .WithUniqueId("SECURE-") .WithHardwareKey(HardwareId.Create().ToMachineKey()) .WithField(Secrets.Source, encryptedPassword) .LicensedTo("Demo Customer", "demo@example.com", "ACME Corp"); string serial = license.SignWith(signature).ToReadableString("ASCII");

LicenseGenerator y la clave privada RSA (keys.pem) son solo para demostración. En producción la licencia la emite el proveedor de software, o el Babel Licensing Service, y la clave privada se mantiene sin conexión. Con la aplicación solo se distribuye la clave pública.

Ejecución

# Build the app in Release — this is when Babel encrypts Secret.Compute. dotnet build src/SecureApp/SecureApp.csproj -c Release # Generate a signed, machine-locked license. dotnet run --project src/LicenseGenerator # Deploy the license next to the app and run it. cp SecureApp.lic src/SecureApp/bin/Release/net8.0/ dotnet src/SecureApp/bin/Release/net8.0/SecureApp.dll

Con una licencia válida, el método protegido se ejecuta:

License OK : SECURE-8CCC8 Licensed to : Demo Customer Bound to machine: ADSQH-89GGJ-J7CJY-3FQFK-DF48H Protected computation Secret.Compute(7, 5) = 147 The encrypted method ran: the license unlocked the code.

Quite o manipule SecureApp.lic, o ejecútelo en otro equipo, y el resultado no es solo un mensaje de error más amable, sino que el código cifrado no puede ejecutarse físicamente:

License INVALID : Invalid license signature Protected computation blocked: Invalid license signature Without a valid license the encrypted code cannot be decrypted or executed.

Por qué es más sólido

Como la contraseña procede de una licencia firmada, un atacante que quite del ensamblado el código de validación no gana nada: la BVM sigue sin tener contraseña para el origen core, de modo que Secret.Compute sigue siendo un bloque opaco y cifrado. La firma impide falsificar una licencia, y la vinculación al hardware impide reutilizar una legítima en otro equipo. Esta misma técnica es la base del ejemplo Licencias basadas en funciones y de las Plantillas de licencia alojadas por el proveedor de software, en las que el campo cifrado lo entrega el Babel Licensing Service en lugar de un generador local.

Last updated on