如何保護軟件版權,最經常使用的辦法就是設計一套license驗證框架。html
1.能夠限制軟件只能在一臺機器上使用; 目前不少軟件都是一機一碼的銷售,軟件換一臺機器則不能使用,想要幾臺機器使用就得購買幾個license; 2.能夠設置一個使用期限; 試用版軟件通常有幾十天的無償使用期,銷售時也能夠分爲一年版、終生版等; 3.能夠設置能使用的權限; 試用版軟件對處理能力有限制,好比短信發送軟件設置發送條數限制,抽獎軟件設置總人數限制,打印軟件試用版插一個軟件廣告等等;
進一步分析以下:ios
試用版:無需License,安裝後的默認版本;有使用期限;有功能限制或插入廣告等;算法
有限期限版:須要License;有使用期限;無功能限制;數據庫
終身免費版:須要License;無限制;數組
通常破解的辦法有如下幾種:安全
1.試用版到期後修改系統時間;服務器
2.試用版到期後找到license文件並修改或刪除;網絡
3.試用版到期後卸載軟件,從新安裝;架構
4.黑客直接反編譯軟件,屏蔽掉驗證License的邏輯;app
針對以上需求,咱們來對應設計License的結構以下:
using System; namespace LicenseDemo { /// <summary> /// License信息 /// </summary> [Serializable] public class LicenseModel { //客戶機器惟一識別碼,由客戶端生成 public string CustomMachineCode { get; set; } //最後使用時間 public DateTime LastUseTime { get; set; } //過時時間expire public DateTime ExpireTime { get; set; } //權限類型(如可分爲 0: 15天試用版 1:1年版 2:終身版) public RoleType CustomRole { get; set; } } /// <summary> /// 幾種角色類型 /// </summary> [Serializable] public enum RoleType { /// <summary> /// 試用版 /// </summary> Trial=0, /// <summary> /// 有期限版 /// </summary> Expiration=1, /// <summary> /// 終身免費版 /// </summary> Free=2 } }
結構說明:
爲何這樣設計就能夠基本達到要求呢?首先一機一碼就要包含客戶機器的惟一標識,能夠經過獲取機器硬件CPU、主板、Bios、Mac地址、顯卡、聲卡等的ID來生成;而後須要有個會員類型來區分是試用版、有限期限版仍是永久免費版;過時時間是用來限制使用時間的,就不用多說;最後使用時間這個字段是爲了防止用戶經過修改系統時間,簡單的跨過試用期限;固然咱們業務層還能夠加一下其餘功能限制或廣告來繼續促成用戶使用正版;
用戶購買License後,這個license如何保存,試用版本的License如何保證即便用戶卸載了軟件重裝,也依然不能改變試用時間。這就要保存好License,能夠放到隱藏系統文件裏面、註冊表裏面、遠程服務器端,安全係數會依次提升;
具體採用什麼方式也跟你的軟件被什麼客戶羣使用有關係,好比你的軟件主要用於上市公司,那麼你都不用擔憂盜版問題,上市公司本身會找你買正版,他得規避法律風險;你的license就能夠放在明處,好比著名的圖像算法處理軟件Haclon,就是每月發佈一次試用版的License,你本身去放到本身的軟件文件夾下面;若是你刪掉他的license,那麼軟件直接打不開。
若是你的客戶是C端我的用戶,那麼就得考慮法律罪責比較難的問題了,只能從增強本身的軟件驗證水平,防破解了;
而後設計一下License分發和驗證的流程:
試用版的客戶端,是不須要license的,軟件第一次啓動時先找一下本地是否有License,若是沒有則默認生成一個試用版License,下次直接讀取到的就是試用版License。後續用戶購買正版License後能夠在軟件中從新激活正版License。
生成客戶機器碼的工具類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Management; using System.Security.Cryptography; namespace LicenseDemo { /// <summary> /// 硬件碼生成器 /// 做者博客:https://www.cnblogs.com/tuyile006/ /// </summary> public class HardwareInfo { private static string myMachineCode = ""; /// <summary> /// 生成一個16字節的機器惟一碼 /// 如: 4876-8DB5-EE85-69D3-FE52-8CF7-395D-2EA9 /// </summary> /// <returns></returns> public static string GetMachineCode() { if (string.IsNullOrEmpty(myMachineCode)) { string omsg = " CPU >> " + CpuId() + " BIOS >> " + BiosId() + " BASE >> " + BaseId(); // + " DISK >> " + DiskId() + " VIDEO >> " + //VideoId() + " MAC >> " + MacId(); myMachineCode = MD5(omsg); } return myMachineCode; } /// <summary> /// MD5哈希加密 /// </summary> /// <param name="scr">原始string數據</param> /// <returns>加密後的數據</returns> private static string MD5(string scr) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] palindata = Encoding.Default.GetBytes(scr);//將要加密的字符串轉換爲字節數組 byte[] encryptdata = md5.ComputeHash(palindata);//將字符串加密後也轉換爲字符數組 return GetHexString(encryptdata);//將加密後的字節數組轉換爲加密字符串 } /// <summary> /// byte[]轉換成十六進制 /// </summary> /// <param name="bt"></param> /// <returns></returns> private static string GetHexString(byte[] bt) { string s = string.Empty; for (int i = 0; i < bt.Length; i++) { byte b = bt[i]; int n, n1, n2; n = (int)b; n1 = n & 15; n2 = (n >> 4) & 15; if (n2 > 9) s += ((char)(n2 - 10 + (int)'A')).ToString(); else s += n2.ToString(); if (n1 > 9) s += ((char)(n1 - 10 + (int)'A')).ToString(); else s += n1.ToString(); if ((i + 1) != bt.Length && (i + 1) % 2 == 0) s += "-"; } return s; } public static string CpuId() { //Uses first CPU identifier available in order of preference //Don't get all identifiers, as it is very time consuming string retVal = identifier("Win32_Processor", "UniqueId"); if (retVal == "") //If no UniqueID, use ProcessorID { retVal = identifier("Win32_Processor", "ProcessorId"); if (retVal == "") //If no ProcessorId, use Name { retVal = identifier("Win32_Processor", "Name"); if (retVal == "") //If no Name, use Manufacturer { retVal = identifier("Win32_Processor", "Manufacturer"); } //Add clock speed for extra security retVal += identifier("Win32_Processor", "MaxClockSpeed"); } } return retVal; } //BIOS Identifier public static string BiosId() { return identifier("Win32_BIOS", "Manufacturer") + identifier("Win32_BIOS", "SMBIOSBIOSVersion") + identifier("Win32_BIOS", "IdentificationCode") + identifier("Win32_BIOS", "SerialNumber") + identifier("Win32_BIOS", "ReleaseDate") + identifier("Win32_BIOS", "Version"); } //Main physical hard drive ID public static string DiskId() { return identifier("Win32_DiskDrive", "Model") + identifier("Win32_DiskDrive", "Manufacturer") + identifier("Win32_DiskDrive", "Signature") + identifier("Win32_DiskDrive", "TotalHeads"); } //Motherboard ID public static string BaseId() { return identifier("Win32_BaseBoard", "Model") + identifier("Win32_BaseBoard", "Manufacturer") + identifier("Win32_BaseBoard", "Name") + identifier("Win32_BaseBoard", "SerialNumber"); } //Primary video controller ID public static string VideoId() { return identifier("Win32_VideoController", "DriverVersion") + identifier("Win32_VideoController", "Name"); } //First enabled network card ID public static string MacId() { return identifier("Win32_NetworkAdapterConfiguration", "MACAddress", "IPEnabled"); } //Return a hardware identifier private static string identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue) { string result = ""; ManagementClass mc = new ManagementClass(wmiClass); ManagementObjectCollection moc = mc.GetInstances(); foreach (ManagementObject mo in moc) { if (mo[wmiMustBeTrue].ToString() == "True") { //Only get the first one if (result == "") { try { result = mo[wmiProperty].ToString(); break; } catch { } } } } return result; } //Return a hardware identifier private static string identifier(string wmiClass, string wmiProperty) { string result = ""; ManagementClass mc = new ManagementClass(wmiClass); ManagementObjectCollection moc = mc.GetInstances(); foreach (ManagementObject mo in moc) { //Only get the first one if (result == "") { try { result = mo[wmiProperty].ToString(); break; } catch { } } } return result; } } }
說明:上面的HardwareInfo類就是幫助生成機器惟一信息的。實際運用中,mac地址、聲卡網卡等容易變更,能夠不加到信息裏面。
對象序列化幫助工具:
using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace LicenseDemo { /// <summary> /// 序列化工具類 /// 做者博客:https://www.cnblogs.com/tuyile006/ /// </summary> public class SerializeHelper { /// <summary> /// 將對象序列化爲二進制數據 /// </summary> /// <param name="obj"></param> /// <returns></returns> public static byte[] SerializeToBinary(object obj) { using (MemoryStream stream = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(stream, obj); byte[] data = stream.ToArray(); stream.Close(); return data; } } /// <summary> /// 將二進制數據反序列化 /// </summary> /// <param name="data"></param> /// <returns></returns> public static object DeserializeWithBinary(byte[] data) { using (MemoryStream stream = new MemoryStream()) { stream.Write(data, 0, data.Length); stream.Position = 0; BinaryFormatter bf = new BinaryFormatter(); object obj = bf.Deserialize(stream); stream.Close(); return obj; } } /// <summary> /// 將二進制數據反序列化爲指定類型對象 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <returns></returns> public static T DeserializeWithBinary<T>(byte[] data) { return (T)DeserializeWithBinary(data); } } }
以及加解密工具:EncodeHelper 源碼見個人另外一篇文章:
using Microsoft.Win32; namespace LicenseDemo { /// <summary> /// 註冊表工件類 /// 做者博客:https://www.cnblogs.com/tuyile006/ /// </summary> public class RegistryHelper { //用於存儲你軟件信息的註冊表菜單名 public static string YourSoftName = "YourSoftName"; /// <summary> /// 獲取你軟件下對應註冊表鍵的值 /// </summary> /// <param name="keyname">鍵名</param> /// <returns></returns> public static string GetRegistData(string keyname) { if (!IsYourSoftkeyExit()) return string.Empty; string registData; RegistryKey aimdir = Registry.LocalMachine.OpenSubKey("SOFTWARE\\"+ YourSoftName, RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl); registData = aimdir.GetValue(keyname).ToString(); return registData; } /// <summary> /// 向你的軟件註冊表菜單下添加鍵值 /// </summary> /// <param name="keyname">鍵名</param> /// <param name="keyvalue">值</param> public static void WriteRegedit(string keyname, string keyvalue) { RegistryKey software = Registry.LocalMachine.OpenSubKey("SOFTWARE", RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl); RegistryKey aimdir ; if (!IsYourSoftkeyExit()) //不存在則建立 { aimdir = software.CreateSubKey(YourSoftName); } else //存在則open { aimdir = software.OpenSubKey(YourSoftName, true); } aimdir.SetValue(keyname, keyvalue,RegistryValueKind.String); aimdir.Close(); } /// <summary> /// 刪除你軟件註冊表菜單下的鍵值 /// </summary> /// <param name="keyname">鍵名</param> public static void DeleteRegist(string keyname) { if (!IsYourSoftkeyExit()) return; string[] aimnames; RegistryKey aimdir = Registry.LocalMachine.OpenSubKey("SOFTWARE\\" + YourSoftName, RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl); aimnames = aimdir.GetValueNames(); foreach (string aimKey in aimnames) { if (aimKey == keyname) aimdir.DeleteValue(keyname); } aimdir.Close(); } /// <summary> /// 判斷你軟件註冊表菜單下鍵是否存在 /// </summary> /// <param name="keyname">鍵名</param> /// <returns></returns> public static bool IsRegeditExit(string keyname) { if (!IsYourSoftkeyExit()) return false; string[] subkeyNames; RegistryKey aimdir = Registry.LocalMachine.OpenSubKey("SOFTWARE\\"+ YourSoftName, RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl); subkeyNames = aimdir.GetValueNames();// GetSubKeyNames(); foreach (string kn in subkeyNames) { if (kn == keyname) { Registry.LocalMachine.Close(); return true; } } return false; } /// <summary> /// 刪除你軟件的註冊表項 /// </summary> public static void DeleteYourSoftKey() { Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\" + YourSoftName); Registry.LocalMachine.Close(); } /// <summary> /// 判斷你軟件的鍵是否存在 /// </summary> /// <returns></returns> private static bool IsYourSoftkeyExit() { using (RegistryKey yourSoftkey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\" + YourSoftName, RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.FullControl)) { return yourSoftkey != null; } } } }
註冊表操做須要用到管理員權限,不然會提示無權限操做註冊表。解決辦法是在項目中添加「app.manifest"文件
並修改manifest中的以下部分。
其實存到註冊表依然不是好辦法,最好仍是將用戶 license保存到服務端,用從服務端請求的方式。或者二者結合,有網絡的時候進行網絡驗證。
最後的License管理器:
using System; namespace LicenseDemo { /// <summary> /// License管理器 /// 做者博客:https://www.cnblogs.com/tuyile006/ /// </summary> public class LicenseManage { /// <summary> /// 當前程序的license 業務層需配合控制權限 /// </summary> public static LicenseModel ApplicationLicense = null; /// <summary> /// 提取客戶機器信息,返回編碼 /// </summary> /// <returns></returns> public static string GetMachineCode() { return HardwareInfo.GetMachineCode(); } private const string regeditkey = "lic";//註冊表鍵名 private const string aeskey = "小y加;&tu@"; //密鑰 /// <summary> /// 服務端生成License文本 可受權給客戶 /// </summary> /// <param name="lic">LicenseModel對象,由客戶提供機器碼,並由商業提供期限和權限角色</param> /// <returns></returns> public static string CreateLicenseString(LicenseModel lic) { byte[] licByte = SerializeHelper.SerializeToBinary(lic); return EncodeHelper.AES(Convert.ToBase64String(licByte), aeskey); } /// <summary> /// 客戶端獲取本地的license 根據本身設計的存儲介質,能夠是從文件中取、也能夠是註冊表或遠程服務器上取。 /// </summary> /// <returns></returns> public static LicenseModel GetLicense() { if (LicenseManage.ApplicationLicense != null) return LicenseManage.ApplicationLicense; try { //若是之前裝過,則從註冊表取值 這裏能夠改爲從數據庫、文件、或服務端 //未取到鍵則建一個 if (!RegistryHelper.IsRegeditExit(regeditkey)) { //第一次使用默認是試用版 LicenseModel license = new LicenseModel() { CustomMachineCode = GetMachineCode(), CustomRole = RoleType.Trial, LastUseTime=DateTime.Now, ExpireTime = DateTime.Now.AddDays(30) }; RegistryHelper.WriteRegedit(regeditkey, CreateLicenseString(license)); LicenseManage.ApplicationLicense = license; } else { string licFromReg = RegistryHelper.GetRegistData(regeditkey); try { string strlic = EncodeHelper.AESDecrypt(licFromReg, aeskey); byte[] licbyte = Convert.FromBase64String(strlic); LicenseModel lm = SerializeHelper.DeserializeWithBinary<LicenseModel>(licbyte); //取到的值還原license並返回 LicenseManage.ApplicationLicense = lm; } catch(Exception ex1) { //_log.Error(ex1); //若是從註冊表中取到的值發現被篡改,則直接試用版到期,不給使用。 LicenseModel licenseErr = new LicenseModel() { CustomMachineCode = GetMachineCode(), CustomRole = RoleType.Trial, LastUseTime = DateTime.Now, ExpireTime = DateTime.Now }; } } } catch(Exception ex) { //_log.Error(ex); } return LicenseManage.ApplicationLicense; } /// <summary> /// 客戶端驗證License,存儲 /// </summary> /// <param name="lic">服務端受權給客戶的License密文</param> /// <returns></returns> public static bool VerifyLicense(string lic) { if(string.IsNullOrEmpty(lic)) return false; try { string strlic = EncodeHelper.AESDecrypt(lic, aeskey); byte[] licbyte = Convert.FromBase64String(strlic); LicenseModel lm = SerializeHelper.DeserializeWithBinary<LicenseModel>(licbyte); //簡單驗證機器碼、role、期限。具體角色權限限制須要在業務系統中實現。 if (VerifyLicense(lm)) { LicenseManage.ApplicationLicense = lm; return true; } } catch { //_log.Error(ex); } //不然直接返回原始試用版 return false; } /// <summary> /// 簡單驗證licensemode對象是否合法,不存儲 /// </summary> /// <param name="licmod"></param> /// <returns></returns> public static bool VerifyLicense(LicenseModel licmod) { //簡單驗證機器碼、role、期限。具體角色權限限制須要在業務系統中實現。 bool isHaveRight = false; if (licmod.CustomMachineCode == GetMachineCode()) { if (licmod.CustomRole == RoleType.Free) { isHaveRight = true; } else if (licmod.LastUseTime < DateTime.Now && licmod.ExpireTime > DateTime.Now) { isHaveRight = true; } } if (isHaveRight) { licmod.LastUseTime = DateTime.Now; RegistryHelper.WriteRegedit(regeditkey, CreateLicenseString(licmod)); } return isHaveRight; } public static void DeleteLicense() { RegistryHelper.DeleteRegist(regeditkey); LicenseManage.ApplicationLicense = null; } } }
管理器的使用Demo以下:
作好了License架構,是否是軟件版權保護就完成了呢?答案是否認的。如今咱們已經造了銀行保險櫃的門,卻只有籬笆同樣容易攻破的牆,黑客直接反編譯你的軟件,去掉權限驗證的邏輯再從新編譯,就能夠終身永久無償使用你的勞動成果,因此不加密的軟件在黑客眼裏就像在裸奔。混淆、加殼是另外一個更加複雜的技術,下面介紹幾款混淆工具。
Eziriz .NET Reactor:
主要功能包括:NecroBit IL(轉爲非託管代碼)、反 ILDASM(反編譯器)、混淆代碼、合併、壓縮源碼、支持命令行等,支持全部 .NET 框架和幾乎全部開發語言,如 C#、C++.NET、VB.NET、Delphi.NET、J# 等等。
使用教程:https://blog.csdn.net/jyxyscf/article/details/78478631
ConfuserEx:
是一款開源.net混淆器。
使用教程:http://www.javashuo.com/article/p-tlwpyhkp-bn.html
Dotfuscator:
這款你們應該比較熟悉了,官方自帶的。