6,717
社区成员
发帖
与我相关
我的任务
分享我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。
本文主要介绍的是许可证形式的授权。
如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护,每个设备都有唯一标识:mac地址,ip地址,主板序列号等,在许可证中指定唯一标识则只能指定设备使用。
为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。
关于Licence验证软件合法性流程图,如下所示:

本实例主要讲解Licence的实际验证过程,分为三部分:
主要通过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; | |
} | |
} | |
} |
主要对客户端提供的电脑信息及有效期等内容,进行非对称加密,如下所示:
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); | |
} | |
} |
主要是加密后的信息,和解密秘钥等内容,保存到文件中,如下所示:
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; | |
} | |
} | |
} |
客户端验证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; | |
} | |
} | |
} | |
} | |
} |
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文件内容,如下所示:

出处:老码识途呀 - 博客园
本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
关注个人公众号,定时同步更新技术及职场文章