C# : comment implémenter une clé de licence simple / appeler checklicence()

la programmation


Existe-t-il un moyen d’appeler automatiquement une méthode CheckLicence() dans chaque méthode d’une classe statique ?

J’ai plusieurs classes statiques avec des centaines de méthodes.
Ainsi, une vérification globale de la licence serait plus confortable que d’appeler la méthode CheckLicence() dans chaque méthode de classe.

Explication de l’exemple :
La licenceKey est définie par l’application au démarrage.
La licence est donc valide pour toute la durée de vie.

Je souhaite me débarrasser de l’appel CheckLicence() dans AddMethod().

Arrière-plan:
J’ai une grande bibliothèque d’outils qui est utilisée dans plusieurs projets. La bibliothèque est déployée dans mes projets (sites web, application bureautique). Je souhaite disposer d’une protection de base afin que les DLL ne puissent pas être directement utilisées par les clients dans leurs propres projets internes.

Ce que j’ai essayé :

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;
    }
}

Solution 1

Non.

C# n’appellera rien automatiquement pour vous – vous devez lui dire explicitement ce que vous voulez appeler et quand.

Vous pouvez toujours ajouter une tâche de thread secondaire qui la vérifie périodiquement et répond au thread principal avec un indicateur « d’échec » qu’il peut traiter à la place ?

Solution 2

Vous pourriez jeter un oeil à AOP (Programmation orientée aspect) : GitHub – Virtuoze/NConcern : NConcern .NET AOP Framework[^]

Mais vous trouverez peut-être que c’est trop compliqué pour ce que vous voulez…

Une autre option consiste à utiliser le bien connu PostSharp extension: PostSharp – Marché Visual Studio[^]

Solution 3

Résolu avec une vérification de licence dans le constructeur de classe statique.
Il s’agit d’une solution très basique pour éviter que ma DLL ne soit réutilisée par d’autres personnes de l’entreprise.
Les gens peuvent trouver très ennuyeux si certaines méthodes fonctionnent et d’autres non si elles ne sont pas “sous licence”, donc cela suffit.
Si la méthode est tellement importante pour changer le monde, le code d’origine peut être facilement restauré à partir de la DLL, donc dans .net, il n’y a de toute façon pas de protection du code sans outils tiers…

Usage:

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

Bibliothèque de classes DLL avec simple vérification de licence :

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()";
        }
    }
}

Solution 4

Clé d’activation

Voici une structure simple de la clé d’activation :

<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>

Obtenir le hachage et le cryptage des données

Cette étape contient les sous-étapes suivantes :

  • crée un moteur de chiffrement à l’aide d’un mot de passe et stocke le vecteur d’initialisation dans le Queue propriété.
  • étape suivante, la date d’expiration et les options sont cryptées et les données cryptées sont enregistrées dans le Données propriété.
  • enfin, le moteur de hachage calcule un hachage en fonction de la date d’expiration, du mot de passe, des options et de l’environnement et le place dans le Hacher propriété.

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;
}

Conversion en chaîne

Utilisez la méthode ToString pour obtenir une chaîne contenant le texte clé, prête à être transférée à l’utilisateur final.

Le codage basé sur N (où N est la base du système numérique) était souvent utilisé pour convertir des données binaires en un texte lisible par l’homme. La clé d’activation la plus couramment utilisée est la base32. L’avantage de cet encodage est un grand alphabet composé de chiffres et de lettres qui ne sont pas sensibles à la casse. L’inconvénient est que cet encodage n’est pas implémenté dans la bibliothèque standard .NET et que vous devez l’implémenter vous-même. Il existe de nombreux exemples d’implémentation base32 sur ce site. Vous pouvez également utiliser le codage hexadécimal et base64 intégrés à mscorlib. Dans mon exemple base32 est utilisé.

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);
    }
}

Vérification

La vérification des clés est effectuée à l’aide des méthodes GetOptions et Verify.

  • ObtenirOptions vérifie la clé et restaure les données intégrées sous forme de tableau d’octets ou null si la clé n’est pas valide.
  • Vérifier vérifie juste la clé.

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;
    }
}

Exemple

Ici est un exemple complet de génération de la clé d’activation en utilisant votre propre combinaison de n’importe quelle quantité de données – texte, chaînes, nombres, octets, etc.

Exemple d’utilisation :

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 : représente la clé d’activation utilisée pour protéger votre application C#.[^]

コメント

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