我們在使用一些需要購買版權的軟體產品時,或者我們做的商業軟體需要進行售賣,為了收取費用,一般需要一個軟體使用許可證,然後輸入這個許可到軟體里就能夠使用軟體。簡單的是一串序列碼或者一個許可證文件,複雜的是一個定製化插件包。於是有的小伙伴就開始好奇這個許可是怎麼實現的,特別是在離線情況下它是怎麼給軟體授... ...
我們在使用一些需要購買版權的軟體產品時,或者我們做的商業軟體需要進行售賣,為了收取費用,一般需要一個軟體使用許可證,然後輸入這個許可到軟體里就能夠使用軟體。簡單的是一串序列碼或者一個許可證文件,複雜的是一個定製化插件包。於是有的小伙伴就開始好奇這個許可是怎麼實現的,特別是在離線情況下它是怎麼給軟體授權,同時又能避免被破解的。
License應用場景
本文主要介紹的是許可證形式的授權。
1. 如何控制只在指定設備上使用
如果不控制指定設備,那麼下發了許可證,只要把軟體複製多份安裝則可到處使用,不利於版權維護,每個設備都有唯一標識:mac地址,ip地址,主板序列號等,在許可證中指定唯一標識則只能指定設備使用。
2. 如何控制軟體使用期限
為了版權可持續性收益,對軟體使用設置期限,到期續費等,則需要在許可證中配置使用起止日期。
Licence實現方案
一、流程設計
- 形式:許可證以文件形式下發,放在客戶端電腦指定位置
- 內容:以上控制內容以dom節點形式放在文件中
- 流程:將控制項加密後寫入license文件節點,部署到客戶機器,客戶機使用時再讀取license文件內容與客戶機實際參數進行匹配校驗
二、文件防破解
- 防止篡改:文件內容加密,使用AES加密,但是AES加密解密都是使用同一個key;使用非對稱公私鑰(本文使用的RSA)對內容加密解密,但是對內容長度有限制;綜合方案,將AES的key(內部定義)用RSA加密,公鑰放在加密工具中,內部持有,私鑰放在解密工具中,引入軟體產品解密使用。
- 防止修改系統時間繞過許可證使用時間:許可證帶上發佈時間戳,並定時修改運行時間記錄到文件,如果系統時間小於這個時間戳,就算大於許可證限制的起始時間也無法使用
- 提高破解難度:懂技術的可以將代碼反編譯過來修改代碼文件直接繞過校驗,所以需要進行代碼混淆,有測試過xjar的混淆效果比較好。
Licence驗證流程圖
關於Licence驗證軟體合法性流程圖,如下所示:
核心源碼
本實例主要講解Licence的實際驗證過程,分為三部分:
- 測試客戶端【LicenceTest】,主要用於模擬客戶端驗證Licence的過程。
- 生成工具【LicenceTool】,主要用於根據客戶生成的電腦文件,生成對應的Licence。
- LicenceCommon,Licence公共通用類,主要實現電腦信息獲取,非對稱加密,文件保存等功能。
1. LicenceCommon
1.1 電腦信息獲取
主要通過ManagementClass進行獲取客戶端電腦硬體相關配置信息,如下所示:
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace DemoLicence.Common
{
public class ComputerHelper
{
public static Dictionary<string,string> GetComputerInfo()
{
var info = new Dictionary<string,string>();
string cpu = GetCPUInfo();
string baseBoard = GetBaseBoardInfo();
string bios = GetBIOSInfo();
string mac = GetMACInfo();
info.Add("cpu", cpu);
info.Add("baseBoard", baseBoard);
info.Add("bios", bios);
info.Add("mac", mac);
return info;
}
private static string GetCPUInfo()
{
string info = string.Empty;
info = GetHardWareInfo("Win32_Processor", "ProcessorId");
return info;
}
private static string GetBIOSInfo()
{
string info = string.Empty;
info = GetHardWareInfo("Win32_BIOS", "SerialNumber");
return info;
}
private static string GetBaseBoardInfo()
{
string info = string.Empty;
info = GetHardWareInfo("Win32_BaseBoard", "SerialNumber");
return info;
}
private static string GetMACInfo()
{
string info = string.Empty;
info = GetMacAddress();//GetHardWareInfo("Win32_NetworkAdapterConfiguration", "MACAddress");
return info;
}
private static string GetMacAddress()
{
var mac = "";
var mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
var moc = mc.GetInstances();
foreach (var o in moc)
{
var mo = (ManagementObject)o;
if (!(bool)mo["IPEnabled"]) continue;
mac = mo["MacAddress"].ToString();
break;
}
return mac;
}
private static string GetHardWareInfo(string typePath, string key)
{
try
{
ManagementClass managementClass = new ManagementClass(typePath);
ManagementObjectCollection mn = managementClass.GetInstances();
PropertyDataCollection properties = managementClass.Properties;
foreach (PropertyData property in properties)
{
if (property.Name == key)
{
foreach (ManagementObject m in mn)
{
return m.Properties[property.Name].Value.ToString();
}
}
}
}
catch (Exception ex)
{
//這裡寫異常的處理
}
return string.Empty;
}
}
}
1.3 RSA非對稱加密
主要對客戶端提供的電腦信息及有效期等內容,進行非對稱加密,如下所示:
public class RSAHelper
{
private static string keyContainerName = "star";
private static string m_PriKey = string.Empty;
private static string m_PubKey = string.Empty;
public static string PriKey
{
get
{
return m_PriKey;
}
set
{
m_PriKey = value;
}
}
public static string PubKey
{
get
{
return m_PubKey;
}
set
{
m_PubKey = value;
}
}
public static string Encrypto(string source)
{
if (string.IsNullOrEmpty(m_PubKey) && string.IsNullOrEmpty(m_PriKey))
{
generateKey();
}
return getEncryptoInfoByRSA(source);
}
public static string Decrypto(string dest)
{
if (string.IsNullOrEmpty(m_PubKey) && string.IsNullOrEmpty(m_PriKey))
{
generateKey();
}
return getDecryptoInfoByRSA(dest);
}
public static void generateKey()
{
CspParameters m_CspParameters;
m_CspParameters = new CspParameters();
m_CspParameters.KeyContainerName = keyContainerName;
RSACryptoServiceProvider asym = new RSACryptoServiceProvider(m_CspParameters);
m_PriKey = asym.ToXmlString(true);
m_PubKey = asym.ToXmlString(false);
asym.PersistKeyInCsp = false;
asym.Clear();
}
private static string getEncryptoInfoByRSA(string source)
{
byte[] plainByte = Encoding.ASCII.GetBytes(source);
//初始化參數
RSACryptoServiceProvider asym = new RSACryptoServiceProvider();
asym.FromXmlString(m_PubKey);
int keySize = asym.KeySize / 8;//非對稱加密,每次的長度不能太長,否則會報異常
int bufferSize = keySize - 11;
if (plainByte.Length > bufferSize)
{
throw new Exception("非對稱加密最多支持【" + bufferSize + "】位元組,實際長度【" + plainByte.Length + "】位元組。");
}
byte[] cryptoByte = asym.Encrypt(plainByte, false);
return Convert.ToBase64String(cryptoByte);
}
private static string getDecryptoInfoByRSA(string dest)
{
byte[] btDest = Convert.FromBase64String(dest);
//初始化參數
RSACryptoServiceProvider asym = new RSACryptoServiceProvider();
asym.FromXmlString(m_PriKey);
int keySize = asym.KeySize / 8;//非對稱加密,每次的長度不能太長,否則會報異常
//int bufferSize = keySize - 11;
if (btDest.Length > keySize)
{
throw new Exception("非對稱解密最多支持【" + keySize + "】位元組,實際長度【" + btDest.Length + "】位元組。");
}
byte[] cryptoByte = asym.Decrypt(btDest, false);
return Encoding.ASCII.GetString(cryptoByte);
}
}
1.3 生成文件
主要是加密後的信息,和解密秘鑰等內容,保存到文件中,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DemoLicence.Common
{
public class RegistFileHelper
{
public static string ComputerInfofile = "ComputerInfo.key";
public static string RegistInfofile = "Licence.key";
public static void WriteRegistFile(string info,string keyFile)
{
string tmp = string.IsNullOrEmpty(keyFile)?RegistInfofile:keyFile;
WriteFile(info, tmp);
}
public static void WriteComputerInfoFile(string info)
{
WriteFile(info, ComputerInfofile);
}
public static string ReadRegistFile(string keyFile)
{
string tmp = string.IsNullOrEmpty(keyFile) ? RegistInfofile : keyFile;
return ReadFile(tmp);
}
public static string ReadComputerInfoFile(string file)
{
string tmp = string.IsNullOrEmpty(file) ? ComputerInfofile : file;
return ReadFile(tmp);
}
private static void WriteFile(string info, string fileName)
{
try
{
using (StreamWriter sw = new StreamWriter(fileName, false))
{
sw.Write(info);
sw.Close();
}
}
catch (Exception ex)
{
}
}
private static string ReadFile(string fileName)
{
string info = string.Empty;
try
{
using (StreamReader sr = new StreamReader(fileName))
{
info = sr.ReadToEnd();
sr.Close();
}
}
catch (Exception ex)
{
}
return info;
}
}
}
以上這三部分,各個功能相互獨立,通過LicenceHelper相互調用,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace DemoLicence.Common
{
public class LicenceHelper
{
/// <summary>
/// 獲取電腦信息,並生成文件
/// </summary>
public static string GetComputerInfoAndGenerateFile()
{
string computerKeyFile = string.Empty;
try
{
var info = GetComputerInfo();
if (info != null && info.Count > 0)
{
//獲取到電腦信息
var strInfo = new StringBuilder();
foreach (var computer in info)
{
strInfo.AppendLine($"{computer.Key}={computer.Value}");
}
RegistFileHelper.WriteComputerInfoFile(strInfo.ToString());
computerKeyFile = RegistFileHelper.ComputerInfofile;
}
}catch(Exception ex)
{
throw ex;
}
return computerKeyFile;
}
public static Dictionary<string,string> GetComputerInfo()
{
var info = ComputerHelper.GetComputerInfo();
return info;
}
public static bool CheckLicenceKeyIsExists()
{
var keyFile = RegistFileHelper.RegistInfofile;
if (File.Exists(keyFile))
{
return true;
}
else
{
return false;
}
}
public static string GetComputerInfo(string computerInfoFile)
{
return RegistFileHelper.ReadComputerInfoFile(computerInfoFile);
}
public static void GenerateLicenceKey(string info,string keyfile)
{
string encrypto = RSAHelper.Encrypto(info);
string priKey = RSAHelper.PriKey;//公鑰加密,私鑰解密
byte[] priKeyBytes = Encoding.ASCII.GetBytes(priKey);
string priKeyBase64=Convert.ToBase64String(priKeyBytes);
StringBuilder keyInfo= new StringBuilder();
keyInfo.AppendLine($"prikey={priKeyBase64}");
keyInfo.AppendLine($"encrypto={encrypto}");
RegistFileHelper.WriteRegistFile(keyInfo.ToString(), keyfile);
}
public static string ReadLicenceKey(string keyfile)
{
var keyInfo = RegistFileHelper.ReadRegistFile(keyfile);
if (keyInfo == null)
{
return string.Empty;
}
string[] keyInfoArr = keyInfo.Split("\r\n");
var priKeyBase64 = keyInfoArr[0].Substring(keyInfoArr[0].IndexOf("=")+1);
var encrypto = keyInfoArr[1].Substring(keyInfoArr[1].IndexOf("=")+1);
var priKeyByte= Convert.FromBase64String(priKeyBase64);
var priKey = Encoding.ASCII.GetString(priKeyByte);
RSAHelper.PriKey= priKey;
var info = RSAHelper.Decrypto(encrypto);
return info;
}
public static string GetDefaultRegisterFileName()
{
return RegistFileHelper.RegistInfofile;
}
public static string GetDefaultComputerFileName()
{
return RegistFileHelper.ComputerInfofile;
}
public static string GetPublicKey()
{
if (string.IsNullOrEmpty(RSAHelper.PubKey))
{
RSAHelper.generateKey();
}
return RSAHelper.PubKey;
}
public static string GetPrivateKey()
{
if (string.IsNullOrEmpty(RSAHelper.PriKey))
{
RSAHelper.generateKey();
}
return RSAHelper.PriKey;
}
}
}
2. 客戶端LicenceTest
客戶端驗證Licence的有效性,當Licence有效時,正常使用軟體,當Licence無效時,則不能正常使用軟體。如下所示:
using DemoLicence.Common;
namespace LicenceTest
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
try
{
string info = string.Empty;
string msg = string.Empty;
//初始化載入
if (LicenceHelper.CheckLicenceKeyIsExists())
{
string keyFile = LicenceHelper.GetDefaultRegisterFileName();
info = LicenceHelper.ReadLicenceKey(keyFile);
}
else
{
var dialogResult = MessageBox.Show("沒有找到預設首選文件,是否手動選擇授權文件?", "詢問", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Title = "請選擇授權文件";
openFileDialog.FileName = LicenceHelper.GetDefaultRegisterFileName();
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
var keyFile = openFileDialog.FileName;
info = LicenceHelper.ReadLicenceKey(keyFile);
//驗證成功後,將手動選擇的文件複製到程式根目錄,且修改為預設名稱
File.Copy(keyFile, LicenceHelper.GetDefaultRegisterFileName());
}
else
{
string computerFile = LicenceHelper.GetComputerInfoAndGenerateFile();
if (!string.IsNullOrEmpty(computerFile))
{
msg = $"您還沒有被授權,請將程式根目錄下的{computerFile}文件,發送到管理員,獲取Licence.";
}
}
}
else
{
string computerFile = LicenceHelper.GetComputerInfoAndGenerateFile();
if (!string.IsNullOrEmpty(computerFile))
{
msg = $"您還沒有被授權,請將程式根目錄下的{computerFile}文件,發送到管理員,獲取Licence.";
}
}
}
if (!string.IsNullOrEmpty(info) && string.IsNullOrEmpty(msg))
{
string[] infos = info.Split("\r\n");
if (infos.Length > 0)
{
var dicInfo = new Dictionary<string, string>();
foreach (var info2 in infos)
{
if (string.IsNullOrEmpty(info2))
{
continue;
}
var info2Arr = info2.Split("=");
dicInfo.Add(info2Arr[0], info2Arr[1]);
}
if (dicInfo.Count > 0)
{
string localMacAddress = string.Empty;
var computerInfo = LicenceHelper.GetComputerInfo();
if (computerInfo != null)
{
localMacAddress = computerInfo["mac"];
}
//比較本地信息和Licence中的信息是否一致
if (localMacAddress == dicInfo["mac"])
{
var endTime = DateTime.Parse(dicInfo["endTime"]);
if (DateTime.Now < endTime)
{
//在有效期內,可以使用
}
else
{
msg = $"軟體授權使用時間範圍:[{endTime}之前],已過期";
}
}
else
{
msg = "軟體Licence不匹配";
}
}
else
{
msg = $"軟體Licence非法.";
}
}
else
{
msg = $"軟體Licence非法.";
}
}
if (!string.IsNullOrEmpty(msg))
{
MessageBox.Show(msg);
foreach (var control in this.Controls)
{
(control as Control).Enabled = false;
}
return;
}
}
catch (Exception ex)
{
string error = $"程式異常,請聯繫管理人員:{ex.Message}\r\n{ex.StackTrace}";
MessageBox.Show(error);
foreach (var control in this.Controls)
{
(control as Control).Enabled = false;
}
}
}
}
}
3. Licence生成工具
LicenceTool主要根據客戶端提供的電腦信息,生成對應的Licence,然後再發送給客戶端,以此達到客戶端電腦的授權使用軟體的目的。如下所示:
using DemoLicence.Common;
using System.Text;
namespace LicenceTool
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
this.txtPublicKey.Text=LicenceHelper.GetPublicKey();
this.txtPrivateKey.Text=LicenceHelper.GetPrivateKey();
}
private void btnBrowser_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "電腦信息文件|*.key";
ofd.Multiselect = false;
ofd.Title = "請選擇電腦信息文件";
ofd.FileName=LicenceHelper.GetDefaultComputerFileName();
if (ofd.ShowDialog() == DialogResult.OK)
{
this.txtSourceFile.Text = ofd.FileName;
}
}
private void btnGenerate_Click(object sender, EventArgs e)
{
try
{
if (string.IsNullOrEmpty(this.txtSourceFile.Text))
{
MessageBox.Show("請先選擇電腦信息文件");
return;
}
if (File.Exists(this.txtSourceFile.Text))
{
//讀取電腦文件
var info = LicenceHelper.GetComputerInfo(this.txtSourceFile.Text);
int days = GetLicenceDays();
var keyInfos = new StringBuilder(info);
var beginTime = DateTime.Now;
var endTime = DateTime.Now.AddDays(days);
//keyInfos.AppendLine($"beginTime={beginTime.ToString("yyyy-MM-dd HH:mm:ss")}");
keyInfos.AppendLine($"endTime={endTime.ToString("yyyy-MM-dd HH:mm:ss")}");
//
info = keyInfos.ToString();
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Title = "保存生成的Licence文件";
saveFileDialog.FileName = LicenceHelper.GetDefaultRegisterFileName();
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
LicenceHelper.GenerateLicenceKey(info, saveFileDialog.FileName);
MessageBox.Show("生成成功");
}
}
else
{
MessageBox.Show("電腦信息文件不存在!");
return;
}
}catch(Exception ex)
{
string error = $"生成出錯:{ex.Message}\r\n{ex.StackTrace}";
MessageBox.Show(error);
}
}
/// <summary>
/// 獲取有效期天數
/// </summary>
/// <returns></returns>
private int GetLicenceDays()
{
int days = 1;
RadioButton[] rbtns = new RadioButton[] { this.rbtSeven, this.rbtnTen, this.rbtnFifteen, this.rbtnThirty, this.rbtnSixty, this.rbtnSixMonth, this.rbtnNinety, this.rbtnSixMonth, this.rbtnForver };
foreach (RadioButton rb in rbtns)
{
if (rb.Checked)
{
if (!int.TryParse(rb.Tag.ToString(), out days))
{
days = 0;
}
break;
}
}
days = days == -1 ? 9999 : days;//永久要轉換一下
return days;
}
}
}
測試驗證
啟動軟體時會進行校驗,在沒有Licence時,會有信息提示,且無法使用軟體,如下所示:
Lincence生成工具
根據客戶提供的電腦信息文件,生成對應的Licence,如下所示:
生成Licence放在客戶端預設目錄下,即可正常使用軟體,如下所示:
註意:非對稱加密每次生成的秘鑰都是不同的,所以需要將解密秘鑰一起保存到生成的Licence文件中,否則秘鑰不同,則無法解密。
生成的電腦信息文件ComputerInfo.key示例如下所示:
生成的Licence.key文件內容,如下所示:
源碼下載
源碼下載可以通過以下3種方式,
1. 公眾號關鍵詞回覆
關註個人公眾號,回覆關鍵字【Licence】獲取源碼,如下所示:
2. 通過gitee(碼雲)下載
本示例中相關源碼,已上傳至gitee(碼雲),鏈接如下:
3. 通過CSDN進行下載
通過CSDN上的資源進行付費下載,不貴不貴,也就一頓早餐的錢。
https://download.csdn.net/download/fengershishe/88294433?spm=1001.2014.3001.5501
以上就是軟體Licence應用實例的全部內容,希望可以拋磚引玉,一起學習,共同進步。學習編程,從關註【老碼識途】開始!!!
作者:小六公子
出處:http://www.cnblogs.com/hsiang/
本文版權歸作者和博客園共有,寫文不易,支持原創,歡迎轉載【點贊】,轉載請保留此段聲明,且在文章頁面明顯位置給出原文連接,謝謝。
關註個人公眾號,定時同步更新技術及職場文章