C#: cómo implementar una clave de licencia simple/llamar a checklicence()

programación


¿Hay alguna manera de llamar automáticamente a un método CheckLicence() en cada método de una clase estática?

Tengo varias clases estáticas con cientos de métodos.
Por lo tanto, una verificación de licencia global sería más cómoda que llamar al método CheckLicence() en cada método de clase.

Explicación del ejemplo:
La clave de licencia la establece la aplicación al inicio.
Entonces la licencia es válida para toda la vida.

Quiero deshacerme de la llamada CheckLicence() en AddMethod().

Fondo:
Tengo una gran biblioteca de herramientas que se utiliza en varios proyectos. La biblioteca se implementa en mis proyectos (sitios web, aplicaciones de escritorio). Quiero tener una protección básica para que los clientes no puedan utilizar directamente la DLL en sus propios proyectos internos.

Lo que he probado:

public static class LicensedClass
{
    public static string licenceKey = "";

    public static bool IsLicensed
    {
        get { return (licenceKey == "ABC123"); }
    }

    public static void CheckLicence()
    {
        if (!IsLicensed) 
                throw new System.ArgumentException("Wrong licence key.", licenceKey);
    }

    public static double AddMethod(double number1, double number2)
    {
        CheckLicence();

        return number1 + number2;
    }
}

Solución 1

No.

C# no llamará a nada automáticamente; debe indicarle explícitamente qué desea llamar y cuándo.

¿Siempre se puede agregar una tarea de subproceso secundario que la verifique periódicamente y responda al subproceso principal con un indicador de “fallido” que puede procesar en su lugar?

Solución 2

Podrías echar un vistazo a POA (Programación Orientada a Aspectos): GitHub – Virtuoze/NConcern: Marco NConcern .NET AOP[^]

Pero puede que encuentres que es demasiado complicado para lo que quieres…

Otra opción es utilizar el conocido PostSharp extensión: PostSharp – Mercado de Visual Studio[^]

Solución 3

Resuelto con una verificación de licencia en el constructor de clases estáticas.
Esta es una solución muy básica para evitar que otras personas de la empresa reutilicen mi dll.
A la gente le puede resultar muy molesto si algunos métodos funcionan y otros no si no tienen “licencia”, así que es suficiente.
Si el método es tan importante que cambia el mundo, el código original se puede restaurar fácilmente desde la dll, por lo que en .net no hay protección de código de todos modos sin herramientas de terceros…

Uso:

2c#”
DllClassLibrary.License.SetLicense("ABC123");
var result = DllClassLibrary.Foo.DoSomething();

Biblioteca de clases DLL con verificación de licencia simple:

C#
namespace DllClassLibrary
{
    public static class License
    {
        private static bool isLicensed = false;
        public static bool SetLicense(string licenseKey)
        {
            isLicensed = (licenseKey == "ABC123");
            return isLicensed;
        }
        public static void CheckLicense()
        {
            if (!isLicensed)
                throw new Exception("License not set.");
        }
    }
    public static class Foo
    {
        static Foo()
        {
            License.CheckLicense();
        }
        public static string DoSomething()
        {
            return $"[{DateTime.Now.ToString()}] DoFoo()";
        }
    }
}

Solución 4

Clave de activación

Aquí hay una estructura simple de la clave de activación:

<code>class ActivationKey
{
    public byte[] Data { get; set; } // Encrypted part.
    public byte[] Hash { get; set; } // Hashed part.
    public byte[] Tail { get; set; } // Initialization vector.
}
</code>
<p>This tool will use cryptographic transformations to generate the key.</p>
<h2>Generating</h2>
<p>The algorithm for obtaining a unique activation key for a data set consists of several steps:</p>
<ul>
<li>data collection,</li>
<li>getting the hash and data encryption,</li>
<li>converting activation key to string.</li>
</ul>
<h3>Data collection</h3>
<p>At this step, you need to get an array of data such as serial number, device ID, expiration date, etc. This purpose can be achieved using the following method:</p>
<pre class="lang-cs prettyprint-override"><code>unsafe byte[] Serialize(params object[] objects)
{
  using (MemoryStream memory = new MemoryStream())
  using (BinaryWriter writer = new BinaryWriter(memory))
  {
    foreach (object obj in objects)
    {
      if (obj == null) continue;
      switch (obj)
      {
        case string str:
          if (str.Length > 0)
            writer.Write(str.ToCharArray());
          continue;
        case DateTime date:
          writer.Write(date.Ticks);
          continue;
        case bool @bool:
          writer.Write(@bool);
          continue;
        case short @short:
          writer.Write(@short);
          continue;
        case ushort @ushort:
          writer.Write(@ushort);
          continue;
        case int @int:
          writer.Write(@int);
          continue;
        case uint @uint:
          writer.Write(@uint);
          continue;
        case long @long:
          writer.Write(@long);
          continue;
        case ulong @ulong:
          writer.Write(@ulong);
          continue;
        case float @float:
          writer.Write(@float);
          continue;
        case double @double:
          writer.Write(@double);
          continue;
        case decimal @decimal:
          writer.Write(@decimal);
          continue;
        case byte[] buffer:
          if (buffer.Length > 0)
            writer.Write(buffer);
          continue;
        case Array array:
          if (array.Length > 0)
            foreach (var a in array) writer.Write(Serialize(a));
          continue;
        case IConvertible conv:
          writer.Write(conv.ToString(CultureInfo.InvariantCulture));
          continue;
        case IFormattable frm:
          writer.Write(frm.ToString(null, CultureInfo.InvariantCulture));
          continue;
        case Stream stream:
          stream.CopyTo(stream);
          continue;
        default:
          try
          {
            int rawsize = Marshal.SizeOf(obj);
            byte[] rawdata = new byte[rawsize];
            GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
            Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
            writer.Write(rawdata);
            handle.Free();
          }
          catch(Exception e)
          {
            // Place debugging tools here.
          }
          continue;
      }
    }
    writer.Flush();
    byte[] bytes = memory.ToArray();
    return bytes;
  }
}
</code>

Obtener el hash y el cifrado de datos

Este paso contiene los siguientes subpasos:

  • crea un motor de cifrado usando una contraseña y almacena el vector de inicialización en el Cola propiedad.
  • siguiente paso, la fecha de vencimiento y las opciones se cifran y los datos cifrados se guardan en el Datos propiedad.
  • Finalmente, el motor de hash calcula un hash basado en la fecha de vencimiento, la contraseña, las opciones y el entorno y lo coloca en el Picadillo propiedad.

ActivationKey Create<TAlg, THash>(DateTime expirationDate, 
                                  object password, 
                                  object options = null, 
                                  params object[] environment)
    where TAlg : SymmetricAlgorithm
    where THash : HashAlgorithm
{
    ActivationKey activationKey = new ActivationKey();
    using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
    {
        if (password == null)
        {
            password = new byte[0];
        }
        activationKey.Tail = cryptoAlg.IV;
        using (DeriveBytes deriveBytes = 
        new PasswordDeriveBytes(Serialize(password), activationKey.Tail))
        {
            cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
        }
        expirationDate = expirationDate.Date;
        long expirationDateStamp = expirationDate.ToBinary();
        using (ICryptoTransform transform = cryptoAlg.CreateEncryptor())
        {
            byte[] data2 = Serialize(expirationDateStamp, options);
            activationKey.Data = transform.TransformFinalBlock(data2, 0, data2.Length);
        }
        using (HashAlgorithm hashAlg = Activator.CreateInstance<THash>())
        {
            byte[] data = Serialize(expirationDateStamp, 
                                    cryptoAlg.Key, 
                                    options, 
                                    environment, 
                                    activationKey.Tail);
            activationKey.Hash = hashAlg.ComputeHash(data);
        }
    }
    return activationKey;
}

Convirtiendo a cadena

Utilice el método ToString para obtener una cadena que contenga el texto clave, lista para transferirla al usuario final.

La codificación basada en N (donde N es la base del sistema numérico) se usaba a menudo para convertir datos binarios en un texto legible por humanos. La clave de activación más utilizada es base32. La ventaja de esta codificación es un alfabeto grande que consta de números y letras que no distinguen entre mayúsculas y minúsculas. La desventaja es que esta codificación no está implementada en la biblioteca estándar .NET y debes implementarla tú mismo. Hay muchos ejemplos de implementación de base32 en este sitio. También puede utilizar la codificación hexadecimal y base64 integrada en mscorlib. en mi ejemplo base32 se utiliza.

string ToString(ActivationKey activationKey)
{
    if (activationKey.Data == null 
       || activationKey.Hash == null 
       || activationKey.Tail == null)
    {
        return string.Empty;
    }
    using (Base32 base32 = new Base32())
    {
        return base32.Encode(Data) + "-" + base32.Encode(Hash) + "-" +
        base32.Encode(Tail);
    }
}

Comprobación

La verificación de claves se lleva a cabo mediante los métodos GetOptions y Verify.

  • Obtener opciones comprueba la clave y restaura los datos incrustados como una matriz de bytes o nulos si la clave no es válida.
  • Verificar solo revisa la clave.

byte[] GetOptions<TAlg, THash>(object password = null, params object[] environment)
    where TAlg : SymmetricAlgorithm
    where THash : HashAlgorithm
{
    if (Data == null || Hash == null || Tail == null)
    {
        return null;
    }
    try
    {
        using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
        {
            cryptoAlg.IV = Tail;
            using (DeriveBytes deriveBytes = 
            new PasswordDeriveBytes(Serialize(password), Tail))
            {
                cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
            }
            using (ICryptoTransform transform = cryptoAlg.CreateDecryptor())
            {
                byte[] data = transform.TransformFinalBlock(Data, 0, Data.Length);
                int optionsLength = data.Length - 8;
                if (optionsLength < 0)
                {
                    return null;
                }
                byte[] options;
                if (optionsLength > 0)
                {
                    options = new byte[optionsLength];
                    Buffer.BlockCopy(data, 8, options, 0, optionsLength);
                }
                else
                {
                    options = new byte[0];
                }
                long expirationDateStamp = BitConverter.ToInt64(data, 0);
                DateTime expirationDate = DateTime.FromBinary(expirationDateStamp);
                if (expirationDate < DateTime.Today)
                {
                    return null;
                }
                using (HashAlgorithm hashAlg = 
                Activator.CreateInstance<THash>())
                {
                    byte[] hash = 
                    hashAlg.ComputeHash(
                         Serialize(expirationDateStamp, 
                                   cryptoAlg.Key, 
                                   options, 
                                   environment, 
                                   Tail));
                    return ByteArrayEquals(Hash, hash) ? options : null;
                }
            }
        }
    }
    catch
    {
        return null;
    }
}

bool Verify<TAlg, THash>(object password = null, params object[] environment)
    where TAlg : SymmetricAlgorithm
    where THash : HashAlgorithm
{
    try
    {
        byte[] key = Serialize(password);
        return Verify<TAlg, THash>(key, environment);
    }
    catch
    {
        return false;
    }
}

Ejemplo

Aquí es un ejemplo completo de cómo generar la clave de activación utilizando su propia combinación de cualquier cantidad de datos: texto, cadenas, números, bytes, etc.

Ejemplo de uso:

string serialNumber = "0123456789"; // The serial number.
const string appName = "myAppName"; // The application name.

// Generating the key. All the parameters passed to the costructor can be omitted.
ActivationKey activationKey = new ActivationKey(
//expirationDate:
DateTime.Now.AddMonths(1),  // Expiration date 1 month later.
                            // Pass DateTime.Max for unlimited use.
//password:
null,                       // Password protection;
                            // this parameter can be null.
//options:
null                       // Pass here numbers, flags, text or other
                           // that you want to restore 
                           // or null if no necessary.
//environment:
appName, serialNumber      // Application name and serial number.
);
// Thus, a simple check of the key for validity is carried out.
bool checkKey = activationKey.Verify((byte[])null, appName, serialNumber);
if (!checkKey)
{
  MessageBox.Show("Your copy is not activated! Please get a valid activation key.");
  Application.Exit();
}

GitHub – ng256/Activation-Key: Representa la clave de activación utilizada para proteger su aplicación C#.[^]

コメント

タイトルとURLをコピーしました