Click here to Skip to main content
15,867,704 members
Please Sign up or sign in to vote.
5.00/5 (1 vote)
See more:
Is there a way to call a CheckLicence() method automatically in every method of a static class?

I have several static classes with hundreds of methods.
So a global licence check would be more comfortable than calling the CheckLicence() method in every class method.

Explanation of the example:
The licenceKey is set by the application at startup.
So the licence is ok for the whole lifetime.

I want to get rid of the CheckLicence() call in AddMethod().

Background:
I have a big tools library which is used in several projects. The library is deployed in my projects (websites, desktop application). I want to have a basic protection so that DLL can not directly be used by customers in their own internal projects.

What I have tried:

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;
    }
}
Posted
Updated 2-Jun-19 22:03pm
v3
Comments
BillWoodruff 22-May-19 11:16am    
"Is there a way to call a CheckLicence() method automatically in every method of a static class?" If you do this, you will have a very slow app. Why would you ever want to check for a license in every method call ... that's crazy.
Sni.DelWoods 23-May-19 2:50am    
You are absolutly right. I've added the reason to my question, so maybe there is a better solution for the problem.

No.

C# will not call anything automatically for you - you have to explicitly tell it what you want to call, and when.

You could always add a secondary thread task that checks it periodically, and responds to the main thread with a "failed" indicator that it can process instead?
 
Share this answer
 
Comments
Sni.DelWoods 23-May-19 2:57am    
This could work, but I don't think thats the best solution. In a non-static class I would have the key integrated in the constructor and checked it once at creating the instance. e.g. var licence = new Licence("ABC123");
In the static class I have actually no idea how to implement this.
johannesnestler 27-May-19 9:42am    
why not do the same within a static contstructor?
Sni.DelWoods 3-Jun-19 3:50am    
The static constructor does not have parameters. I just want to add a very basic license key to implement a very simple solution to avoid that other people use my dll libary in their own project. So setting a simple fixed password is enough. A simple license key file which is loaded in the construtor would not solve the problem as the file could easily be copied.
You could have a look at AOP (Aspect Oriented Programming): GitHub - Virtuoze/NConcern: NConcern .NET AOP Framework[^]
But you might find it's too complicated for what you want ...

Another option is using the well known PostSharp extension: PostSharp - Visual Studio Marketplace[^]
 
Share this answer
 
v2
Comments
Sni.DelWoods 23-May-19 3:03am    
The lib is very interessting, but this is too over the top for my problem.
Richard Deeming 24-May-19 14:46pm    
FYI:
"NConcern has been redesigned, improved and made reliable through the Puresharp[^] project."
RickZeeland 24-May-19 16:09pm    
Thanks Richard, very interesting, and of course there's the well known PostSharp extension: https://marketplace.visualstudio.com/items?itemName=PostSharpTechnologies.PostSharp
Solved with a license check in static class constructor.
This is a very basic solutions for saving my dll from being reused by other people in company.
People may find it very annoying if some methods work and other do not if it is not "licensed", so that's enought.
If the method is sooo world-changing important the original code can be easily restored from the dll, so in .net there is no code protection anyway without 3rd party tools...

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

DLL-Class-Library with simple license check:
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()";
        }
    }
}
 
Share this answer
 
v2

Activation Key


Here is a simple structure of the activation key:


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

Getting the hash and data encryption


This step contains the following substeps:



  • create an encryption engine using a password and stores the initialization vector in the Tail property.
  • next step, expiration date and options are encrypted and the encrypted data is saved into the Data property.
  • finally, the hashing engine calculates a hash based on the expiration date, password, options and environment and puts it in the Hash property.

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

Converting to string


Use the ToString method to get a string containing the key text, ready to be transfering to the end user.


N-based encoding (where N is the base of the number system) was often used to convert binary data into a human-readable text. The most commonly used in activation key is base32. The advantage of this encoding is a large alphabet consisting of numbers and letters that case insensitive. The downside is that this encoding is not implemented in the .NET standard library and you should implement it yourself. There are many examples of base32 implementation on this site. You can also use the hex encoding and base64 built into mscorlib. In my example base32 is used.


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

Checking


Key verification is carried out using methodes GetOptions an Verify.



  • GetOptions checks the key and restores embeded data as byte array or null if key is not valid.
  • Verify just checks the key.

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

Example


Here is a full example of generating the activation key using your own combination of any amount of data - text, strings, numbers, bytes, etc.


Example of usage:


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: Represents the activation key used to protect your C# application.[^]
 
Share this answer
 
Comments
Graeme_Grant 21-Mar-24 2:03am    
This should be posted as an article or tip[^], not an solution to a 5 year old question where the answer was already accepted. It will get lost here!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900