C#: cách triển khai khóa cấp phép/kiểm tra cuộc gọi đơn giản()

lập trình


Có cách nào để tự động gọi phương thức CheckLicence() trong mọi phương thức của lớp tĩnh không?

Tôi có một số lớp tĩnh với hàng trăm phương thức.
Vì vậy, việc kiểm tra giấy phép toàn cầu sẽ thoải mái hơn việc gọi phương thức CheckLicence() trong mọi phương thức lớp.

Giải thích ví dụ:
LicenseKey được ứng dụng đặt khi khởi động.
Vì vậy, giấy phép là ok cho cả đời.

Tôi muốn loại bỏ lệnh gọi CheckLicence() trong AddMethod().

Lý lịch:
Tôi có một thư viện công cụ lớn được sử dụng trong một số dự án. Thư viện được triển khai trong các dự án của tôi (trang web, ứng dụng máy tính để bàn). Tôi muốn có một biện pháp bảo vệ cơ bản để khách hàng không thể trực tiếp sử dụng DLL trong các dự án nội bộ của chính họ.

Những gì tôi đã thử:

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

Giải pháp 1

KHÔNG.

C# sẽ không tự động gọi bất cứ thứ gì cho bạn – bạn phải nói rõ ràng với nó những gì bạn muốn gọi và khi nào.

Bạn luôn có thể thêm tác vụ của luồng phụ để kiểm tra định kỳ và phản hồi luồng chính bằng chỉ báo “không thành công” mà thay vào đó nó có thể xử lý?

Giải pháp 2

Bạn có thể xem qua AOP (Lập trình hướng theo khía cạnh): GitHub – Virtuoze/NCConcern: NCConcern .NET AOP Framework[^]

Nhưng bạn có thể thấy nó quá phức tạp so với những gì bạn muốn …

Một lựa chọn khác là sử dụng cái nổi tiếng bài viết sắc nét sự mở rộng: PostSharp – Thị trường Visual Studio[^]

Giải pháp 3

Đã giải quyết bằng cách kiểm tra giấy phép trong hàm tạo lớp tĩnh.
Đây là một giải pháp rất cơ bản để cứu dll của tôi khỏi bị người khác trong công ty sử dụng lại.
Mọi người có thể cảm thấy rất khó chịu nếu một số phương pháp hoạt động còn những phương pháp khác thì không nếu nó không được “cấp phép”, thế là đủ.
Nếu phương thức này rất quan trọng, thì mã gốc có thể được khôi phục dễ dàng từ dll, do đó, trong .net dù sao cũng không có biện pháp bảo vệ mã nào nếu không có công cụ của bên thứ 3…

Cách sử dụng:

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

Thư viện lớp DLL với kiểm tra giấy phép đơn giản:

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

Giải pháp 4

Mã kích hoạt

Đây là cấu trúc đơn giản của khóa kích hoạt:

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

Lấy hàm băm và mã hóa dữ liệu

Bước này bao gồm các bước phụ sau:

  • tạo một công cụ mã hóa bằng mật khẩu và lưu trữ vectơ khởi tạo trong Đuôi tài sản.
  • bước tiếp theo, ngày hết hạn và các tùy chọn được mã hóa và dữ liệu được mã hóa sẽ được lưu vào Dữ liệu tài sản.
  • cuối cùng, công cụ băm tính toán hàm băm dựa trên ngày hết hạn, mật khẩu, tùy chọn và môi trường rồi đặt nó vào Băm tài sản.

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

Chuyển đổi thành chuỗi

Sử dụng phương thức ToString để lấy một chuỗi chứa văn bản chính, sẵn sàng chuyển đến người dùng cuối.

Mã hóa dựa trên N (trong đó N là cơ sở của hệ thống số) thường được sử dụng để chuyển đổi dữ liệu nhị phân thành văn bản mà con người có thể đọc được. Khóa kích hoạt được sử dụng phổ biến nhất là base32. Ưu điểm của cách mã hóa này là bảng chữ cái lớn bao gồm các số và chữ cái không phân biệt chữ hoa chữ thường. Nhược điểm là mã hóa này không được triển khai trong thư viện chuẩn .NET và bạn nên tự triển khai nó. Có rất nhiều ví dụ về việc triển khai base32 trên trang này. Bạn cũng có thể sử dụng mã hóa hex và base64 được tích hợp trong mscorlib. Trong ví dụ của tôi cơ sở32 Được sử dụng.

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

Kiểm tra

Việc xác minh khóa được thực hiện bằng các phương thức GetOptions và Verify.

  • Nhận tùy chọn kiểm tra khóa và khôi phục dữ liệu được nhúng dưới dạng mảng byte hoặc null nếu khóa không hợp lệ.
  • Xác minh chỉ cần kiểm tra chìa khóa.

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

Ví dụ

Đây là một ví dụ đầy đủ về việc tạo khóa kích hoạt bằng cách sử dụng kết hợp bất kỳ lượng dữ liệu nào của riêng bạn – văn bản, chuỗi, số, byte, v.v.

Ví dụ về cách sử dụng:

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: Thể hiện khóa kích hoạt được sử dụng để bảo vệ ứng dụng C# của bạn.[^]

コメント

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