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.gitEl 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.dllCon 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.