C#: كيفية تنفيذ مفتاح ترخيص بسيط/فحص ترخيص الاتصال ()


هل هناك طريقة لاستدعاء طريقة CheckLicence() تلقائيًا في كل طريقة لفئة ثابتة؟

لدي عدة فئات ثابتة مع مئات الطرق.
لذا فإن فحص الترخيص العام سيكون أكثر راحة من استدعاء طريقة CheckLicence() في كل طريقة فئة.

شرح المثال:
يتم تعيين مفتاح الترخيص بواسطة التطبيق عند بدء التشغيل.
وبالتالي فإن الترخيص على ما يرام طوال العمر.

أريد التخلص من استدعاء CheckLicence() في AddMethod().

خلفية:
لدي مكتبة أدوات كبيرة تستخدم في العديد من المشاريع. المكتبة منتشرة في مشاريعي (مواقع الويب، تطبيقات سطح المكتب). أريد الحصول على حماية أساسية حتى لا يتمكن العملاء من استخدام DLL بشكل مباشر في مشاريعهم الداخلية.

ما حاولت:

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

الحل 1

لا.

لن تقوم لغة C# بالاتصال بأي شيء تلقائيًا نيابةً عنك – عليك أن تخبرها صراحةً بما تريد الاتصال به ومتى.

هل يمكنك دائمًا إضافة مهمة سلسلة رسائل ثانوية تقوم بفحصها بشكل دوري، وتستجيب للسلسلة الرئيسية بمؤشر “فشل” يمكنه معالجتها بدلاً من ذلك؟

الحل 2

هل يمكن أن نلقي نظرة على AOP (الجانب برمجة): GitHub – Virtuoze/NConcern: NConcern .NET AOP Framework[^]

ولكن قد تجد أن الأمر معقد للغاية بالنسبة لما تريده …

خيار آخر هو استخدام المعروفة بوستشارب امتداد: PostSharp – سوق Visual Studio[^]

الحل 3

تم الحل مع فحص الترخيص في مُنشئ الفئة الثابتة.
هذا هو الحل الأساسي للغاية لحفظ ملف dll الخاص بي من إعادة استخدامه من قبل أشخاص آخرين في الشركة.
قد يجد الناس الأمر مزعجًا للغاية إذا كانت بعض الأساليب تعمل والبعض الآخر لا يعمل إذا لم تكن “مرخصة”، لذا فهذا يكفي.
إذا كانت الطريقة مهمة جدًا لتغيير العالم، فيمكن استعادة الكود الأصلي بسهولة من ملف dll، لذلك في .net لا توجد حماية للكود على أي حال بدون أدوات الطرف الثالث…

الاستخدام:

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

مكتبة DLL-Class مع فحص ترخيص بسيط:

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

الحل 4

مفتاح التفعيل

فيما يلي بنية بسيطة لمفتاح التنشيط:

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

الحصول على التجزئة وتشفير البيانات

تحتوي هذه الخطوة على الخطوات الفرعية التالية:

  • إنشاء محرك تشفير باستخدام كلمة مرور وتخزين متجه التهيئة في الملف ذيل ملكية.
  • الخطوة التالية، يتم تشفير تاريخ انتهاء الصلاحية والخيارات ويتم حفظ البيانات المشفرة في الملف بيانات ملكية.
  • وأخيرًا، يقوم محرك التجزئة بحساب التجزئة بناءً على تاريخ انتهاء الصلاحية وكلمة المرور والخيارات والبيئة ويضعها في التجزئة ملكية.

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

التحويل إلى سلسلة

استخدم طريقة ToString للحصول على سلسلة تحتوي على النص الرئيسي، جاهزة للنقل إلى المستخدم النهائي.

غالبًا ما يتم استخدام التشفير المستند إلى N (حيث N هو أساس نظام الأرقام) لتحويل البيانات الثنائية إلى نص يمكن قراءته بواسطة الإنسان. المفتاح الأكثر استخدامًا في التنشيط هو base32. ميزة هذا التشفير هي أبجدية كبيرة تتكون من أرقام وحروف غير حساسة لحالة الأحرف. الجانب السلبي هو أن هذا التشفير لم يتم تنفيذه في مكتبة .NET القياسية ويجب عليك تنفيذه بنفسك. هناك العديد من الأمثلة على تطبيق Base32 على هذا الموقع. يمكنك أيضًا استخدام التشفير السداسي وbase64 المدمج في mscorlib. في المثال الخاص بي base32 يستخدم.

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

تدقيق

يتم إجراء التحقق من المفتاح باستخدام طرق GetOptions والتحقق.

  • GetOptions يتحقق من المفتاح ويستعيد البيانات المضمنة كمصفوفة بايت أو خالية إذا كان المفتاح غير صالح.
  • يؤكد فقط يتحقق من المفتاح.

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

مثال

هنا يعد هذا مثالًا كاملاً على إنشاء مفتاح التنشيط باستخدام مجموعتك الخاصة من أي كمية من البيانات – النص، والسلاسل، والأرقام، والبايت، وما إلى ذلك.

مثال للاستخدام:

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: يمثل مفتاح التنشيط المستخدم لحماية تطبيق C# الخاص بك.[^]

コメント

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