我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。
License应用场景
本文主要介绍的是许可证形式的授权。
1. 如何控制只在指定设备上使用
如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护,每个设备都有唯一标识:mac地址,ip地址,主板序列号等,在许可证中指定唯一标识则只能指定设备使用。
2. 如何控制软件使用期限
为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。
Licence实现方案
1. 流程设计
- 形式:许可证以文件形式下发,放在客户端电脑指定位置
- 内容:以上控制内容以dom节点形式放在文件中
- 流程:将控制项加密后写入license文件节点,部署到客户机器,客户机使用时再读取license文件内容与客户机实际参数进行匹配校验
2. 文件防破解
- 防止篡改:文件内容加密,使用AES加密,但是AES加密解密都是使用同一个key;使用非对称公私钥(本文使用的RSA)对内容加密解密,但是对内容长度有限制;综合方案,将AES的key(内部定义)用RSA加密,公钥放在加密工具中,内部持有,私钥放在解密工具中,引入软件产品解密使用。
- 防止修改系统时间绕过许可证使用时间:许可证带上发布时间戳,并定时修改运行时间记录到文件,如果系统时间小于这个时间戳,就算大于许可证限制的起始时间也无法使用
- 提高破解难度:懂技术的可以将代码反编译过来修改代码文件直接绕过校验,所以需要进行代码混淆,有测试过xjar的混淆效果比较好。
Licence验证流程图
关于Licence验证软件合法性流程图,如下所示:
核心源码
本实例主要讲解Licence的实际验证过程,分为三部分:
- 测试客户端【LicenceTest】,主要用于模拟客户端验证Licence的过程。
- 生成工具【LicenceTool】,主要用于根据客户生成的电脑文件,生成对应的Licence。
- LicenceCommon,Licence公共通用类,主要实现电脑信息获取,非对称加密,文件保存等功能。
1. LicenceCommon
1.1 电脑信息获取
主要通过ManagementClass进行获取客户端电脑硬件相关配置信息,如下所示:
usingMicrosoft.Win32; usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Management; usingSystem.Net.NetworkInformation; usingSystem.Text; usingSystem.Threading.Tasks; namespaceDemoLicence.Common{ publicclassComputerHelper { publicstaticDictionary<string,string>GetComputerInfo() { varinfo=newDictionary<string,string>(); stringcpu=GetCPUInfo(); stringbaseBoard=GetBaseBoardInfo(); stringbios=GetBIOSInfo(); stringmac=GetMACInfo(); info.Add("cpu", cpu); info.Add("baseBoard", baseBoard); info.Add("bios", bios); info.Add("mac", mac); returninfo; } privatestaticstringGetCPUInfo() { stringinfo=string.Empty; info=GetHardWareInfo("Win32_Processor", "ProcessorId"); returninfo; } privatestaticstringGetBIOSInfo() { stringinfo=string.Empty; info=GetHardWareInfo("Win32_BIOS", "SerialNumber"); returninfo; } privatestaticstringGetBaseBoardInfo() { stringinfo=string.Empty; info=GetHardWareInfo("Win32_BaseBoard", "SerialNumber"); returninfo; } privatestaticstringGetMACInfo() { stringinfo=string.Empty; info=GetMacAddress();//GetHardWareInfo("Win32_NetworkAdapterConfiguration", "MACAddress");returninfo; } privatestaticstringGetMacAddress() { varmac=""; varmc=newManagementClass("Win32_NetworkAdapterConfiguration"); varmoc=mc.GetInstances(); foreach (varoinmoc) { varmo= (ManagementObject)o; if (!(bool)mo["IPEnabled"]) continue; mac=mo["MacAddress"].ToString(); break; } returnmac; } privatestaticstringGetHardWareInfo(stringtypePath, stringkey) { try { ManagementClassmanagementClass=newManagementClass(typePath); ManagementObjectCollectionmn=managementClass.GetInstances(); PropertyDataCollectionproperties=managementClass.Properties; foreach (PropertyDatapropertyinproperties) { if (property.Name==key) { foreach (ManagementObjectminmn) { returnm.Properties[property.Name].Value.ToString(); } } } } catch (Exceptionex) { //这里写异常的处理 } returnstring.Empty; } } }
1.3 RSA非对称加密
主要对客户端提供的电脑信息及有效期等内容,进行非对称加密,如下所示:
publicclassRSAHelper{ privatestaticstringkeyContainerName="star"; privatestaticstringm_PriKey=string.Empty; privatestaticstringm_PubKey=string.Empty; publicstaticstringPriKey { get { returnm_PriKey; } set { m_PriKey=value; } } publicstaticstringPubKey { get { returnm_PubKey; } set { m_PubKey=value; } } publicstaticstringEncrypto(stringsource) { if (string.IsNullOrEmpty(m_PubKey) &&string.IsNullOrEmpty(m_PriKey)) { generateKey(); } returngetEncryptoInfoByRSA(source); } publicstaticstringDecrypto(stringdest) { if (string.IsNullOrEmpty(m_PubKey) &&string.IsNullOrEmpty(m_PriKey)) { generateKey(); } returngetDecryptoInfoByRSA(dest); } publicstaticvoidgenerateKey() { CspParametersm_CspParameters; m_CspParameters=newCspParameters(); m_CspParameters.KeyContainerName=keyContainerName; RSACryptoServiceProviderasym=newRSACryptoServiceProvider(m_CspParameters); m_PriKey=asym.ToXmlString(true); m_PubKey=asym.ToXmlString(false); asym.PersistKeyInCsp=false; asym.Clear(); } privatestaticstringgetEncryptoInfoByRSA(stringsource) { byte[] plainByte=Encoding.ASCII.GetBytes(source); //初始化参数RSACryptoServiceProviderasym=newRSACryptoServiceProvider(); asym.FromXmlString(m_PubKey); intkeySize=asym.KeySize/8;//非对称加密,每次的长度不能太长,否则会报异常intbufferSize=keySize-11; if (plainByte.Length>bufferSize) { thrownewException("非对称加密最多支持【"+bufferSize+"】字节,实际长度【"+plainByte.Length+"】字节。"); } byte[] cryptoByte=asym.Encrypt(plainByte, false); returnConvert.ToBase64String(cryptoByte); } privatestaticstringgetDecryptoInfoByRSA(stringdest) { byte[] btDest=Convert.FromBase64String(dest); //初始化参数RSACryptoServiceProviderasym=newRSACryptoServiceProvider(); asym.FromXmlString(m_PriKey); intkeySize=asym.KeySize/8;//非对称加密,每次的长度不能太长,否则会报异常//int bufferSize = keySize - 11;if (btDest.Length>keySize) { thrownewException("非对称解密最多支持【"+keySize+"】字节,实际长度【"+btDest.Length+"】字节。"); } byte[] cryptoByte=asym.Decrypt(btDest, false); returnEncoding.ASCII.GetString(cryptoByte); } }
1.3 生成文件
主要是加密后的信息,和解密秘钥等内容,保存到文件中,如下所示:
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingSystem.Threading.Tasks; namespaceDemoLicence.Common{ publicclassRegistFileHelper { publicstaticstringComputerInfofile="ComputerInfo.key"; publicstaticstringRegistInfofile="Licence.key"; publicstaticvoidWriteRegistFile(stringinfo,stringkeyFile) { stringtmp=string.IsNullOrEmpty(keyFile)?RegistInfofile:keyFile; WriteFile(info, tmp); } publicstaticvoidWriteComputerInfoFile(stringinfo) { WriteFile(info, ComputerInfofile); } publicstaticstringReadRegistFile(stringkeyFile) { stringtmp=string.IsNullOrEmpty(keyFile) ?RegistInfofile : keyFile; returnReadFile(tmp); } publicstaticstringReadComputerInfoFile(stringfile) { stringtmp=string.IsNullOrEmpty(file) ?ComputerInfofile : file; returnReadFile(tmp); } privatestaticvoidWriteFile(stringinfo, stringfileName) { try { using (StreamWritersw=newStreamWriter(fileName, false)) { sw.Write(info); sw.Close(); } } catch (Exceptionex) { } } privatestaticstringReadFile(stringfileName) { stringinfo=string.Empty; try { using (StreamReadersr=newStreamReader(fileName)) { info=sr.ReadToEnd(); sr.Close(); } } catch (Exceptionex) { } returninfo; } } }
以上这三部分,各个功能相互独立,通过LicenceHelper相互调用,如下所示:
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Runtime.CompilerServices; usingSystem.Text; usingSystem.Threading.Tasks; namespaceDemoLicence.Common{ publicclassLicenceHelper { /// <summary>/// 获取电脑信息,并生成文件/// </summary>publicstaticstringGetComputerInfoAndGenerateFile() { stringcomputerKeyFile=string.Empty; try { varinfo=GetComputerInfo(); if (info!=null&&info.Count>0) { //获取到电脑信息varstrInfo=newStringBuilder(); foreach (varcomputerininfo) { strInfo.AppendLine($"{computer.Key}={computer.Value}"); } RegistFileHelper.WriteComputerInfoFile(strInfo.ToString()); computerKeyFile=RegistFileHelper.ComputerInfofile; } }catch(Exceptionex) { throwex; } returncomputerKeyFile; } publicstaticDictionary<string,string>GetComputerInfo() { varinfo=ComputerHelper.GetComputerInfo(); returninfo; } publicstaticboolCheckLicenceKeyIsExists() { varkeyFile=RegistFileHelper.RegistInfofile; if (File.Exists(keyFile)) { returntrue; } else { returnfalse; } } publicstaticstringGetComputerInfo(stringcomputerInfoFile) { returnRegistFileHelper.ReadComputerInfoFile(computerInfoFile); } publicstaticvoidGenerateLicenceKey(stringinfo,stringkeyfile) { stringencrypto=RSAHelper.Encrypto(info); stringpriKey=RSAHelper.PriKey;//公钥加密,私钥解密byte[] priKeyBytes=Encoding.ASCII.GetBytes(priKey); stringpriKeyBase64=Convert.ToBase64String(priKeyBytes); StringBuilderkeyInfo=newStringBuilder(); keyInfo.AppendLine($"prikey={priKeyBase64}"); keyInfo.AppendLine($"encrypto={encrypto}"); RegistFileHelper.WriteRegistFile(keyInfo.ToString(), keyfile); } publicstaticstringReadLicenceKey(stringkeyfile) { varkeyInfo=RegistFileHelper.ReadRegistFile(keyfile); if (keyInfo==null) { returnstring.Empty; } string[] keyInfoArr=keyInfo.Split("\r\n"); varpriKeyBase64=keyInfoArr[0].Substring(keyInfoArr[0].IndexOf("=")+1); varencrypto=keyInfoArr[1].Substring(keyInfoArr[1].IndexOf("=")+1); varpriKeyByte=Convert.FromBase64String(priKeyBase64); varpriKey=Encoding.ASCII.GetString(priKeyByte); RSAHelper.PriKey=priKey; varinfo=RSAHelper.Decrypto(encrypto); returninfo; } publicstaticstringGetDefaultRegisterFileName() { returnRegistFileHelper.RegistInfofile; } publicstaticstringGetDefaultComputerFileName() { returnRegistFileHelper.ComputerInfofile; } publicstaticstringGetPublicKey() { if (string.IsNullOrEmpty(RSAHelper.PubKey)) { RSAHelper.generateKey(); } returnRSAHelper.PubKey; } publicstaticstringGetPrivateKey() { if (string.IsNullOrEmpty(RSAHelper.PriKey)) { RSAHelper.generateKey(); } returnRSAHelper.PriKey; } } }
2. 客户端LicenceTest
客户端验证Licence的有效性,当Licence有效时,正常使用软件,当Licence无效时,则不能正常使用软件。如下所示:
usingDemoLicence.Common; namespaceLicenceTest{ publicpartialclassMainForm : Form { publicMainForm() { InitializeComponent(); } privatevoidMainForm_Load(objectsender, EventArgse) { try { stringinfo=string.Empty; stringmsg=string.Empty; //初始化加载if (LicenceHelper.CheckLicenceKeyIsExists()) { stringkeyFile=LicenceHelper.GetDefaultRegisterFileName(); info=LicenceHelper.ReadLicenceKey(keyFile); } else { vardialogResult=MessageBox.Show("没有找到默认首选文件,是否手动选择授权文件?", "询问", MessageBoxButtons.YesNo); if (dialogResult==DialogResult.Yes) { OpenFileDialogopenFileDialog=newOpenFileDialog(); openFileDialog.Title="请选择授权文件"; openFileDialog.FileName=LicenceHelper.GetDefaultRegisterFileName(); if (openFileDialog.ShowDialog() ==DialogResult.OK) { varkeyFile=openFileDialog.FileName; info=LicenceHelper.ReadLicenceKey(keyFile); //验证成功后,将手动选择的文件复制到程序根目录,且修改为默认名称File.Copy(keyFile, LicenceHelper.GetDefaultRegisterFileName()); } else { stringcomputerFile=LicenceHelper.GetComputerInfoAndGenerateFile(); if (!string.IsNullOrEmpty(computerFile)) { msg=$"您还没有被授权,请将程序根目录下的{computerFile}文件,发送到管理员,获取Licence."; } } } else { stringcomputerFile=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) { vardicInfo=newDictionary<string, string>(); foreach (varinfo2ininfos) { if (string.IsNullOrEmpty(info2)) { continue; } varinfo2Arr=info2.Split("="); dicInfo.Add(info2Arr[0], info2Arr[1]); } if (dicInfo.Count>0) { stringlocalMacAddress=string.Empty; varcomputerInfo=LicenceHelper.GetComputerInfo(); if (computerInfo!=null) { localMacAddress=computerInfo["mac"]; } //比较本地信息和Licence中的信息是否一致if (localMacAddress==dicInfo["mac"]) { varendTime=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 (varcontrolinthis.Controls) { (controlasControl).Enabled=false; } return; } } catch (Exceptionex) { stringerror=$"程序异常,请联系管理人员:{ex.Message}\r\n{ex.StackTrace}"; MessageBox.Show(error); foreach (varcontrolinthis.Controls) { (controlasControl).Enabled=false; } } } } }
3. Licence生成工具
LicenceTool主要根据客户端提供的电脑信息,生成对应的Licence,然后再发送给客户端,以此达到客户端电脑的授权使用软件的目的。如下所示:
usingDemoLicence.Common; usingSystem.Text; namespaceLicenceTool{ publicpartialclassMainForm : Form { publicMainForm() { InitializeComponent(); } privatevoidMainForm_Load(objectsender, EventArgse) { this.txtPublicKey.Text=LicenceHelper.GetPublicKey(); this.txtPrivateKey.Text=LicenceHelper.GetPrivateKey(); } privatevoidbtnBrowser_Click(objectsender, EventArgse) { OpenFileDialogofd=newOpenFileDialog(); ofd.Filter="电脑信息文件|*.key"; ofd.Multiselect=false; ofd.Title="请选择电脑信息文件"; ofd.FileName=LicenceHelper.GetDefaultComputerFileName(); if (ofd.ShowDialog() ==DialogResult.OK) { this.txtSourceFile.Text=ofd.FileName; } } privatevoidbtnGenerate_Click(objectsender, EventArgse) { try { if (string.IsNullOrEmpty(this.txtSourceFile.Text)) { MessageBox.Show("请先选择电脑信息文件"); return; } if (File.Exists(this.txtSourceFile.Text)) { //读取电脑文件varinfo=LicenceHelper.GetComputerInfo(this.txtSourceFile.Text); intdays=GetLicenceDays(); varkeyInfos=newStringBuilder(info); varbeginTime=DateTime.Now; varendTime=DateTime.Now.AddDays(days); //keyInfos.AppendLine($"beginTime={beginTime.ToString("yyyy-MM-dd HH:mm:ss")}");keyInfos.AppendLine($"endTime={endTime.ToString("yyyy-MM-ddHH:mm:ss")}"); //info=keyInfos.ToString(); SaveFileDialogsaveFileDialog=newSaveFileDialog(); saveFileDialog.Title="保存生成的Licence文件"; saveFileDialog.FileName=LicenceHelper.GetDefaultRegisterFileName(); if (saveFileDialog.ShowDialog() ==DialogResult.OK) { LicenceHelper.GenerateLicenceKey(info, saveFileDialog.FileName); MessageBox.Show("生成成功"); } } else { MessageBox.Show("电脑信息文件不存在!"); return; } }catch(Exceptionex) { stringerror=$"生成出错:{ex.Message}\r\n{ex.StackTrace}"; MessageBox.Show(error); } } /// <summary>/// 获取有效期天数/// </summary>/// <returns></returns>privateintGetLicenceDays() { intdays=1; RadioButton[] rbtns=newRadioButton[] { this.rbtSeven, this.rbtnTen, this.rbtnFifteen, this.rbtnThirty, this.rbtnSixty, this.rbtnSixMonth, this.rbtnNinety, this.rbtnSixMonth, this.rbtnForver }; foreach (RadioButtonrbinrbtns) { if (rb.Checked) { if (!int.TryParse(rb.Tag.ToString(), outdays)) { days=0; } break; } } days=days==-1?9999 : days;//永久要转换一下returndays; } } }
测试验证
启动软件时会进行校验,在没有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应用实例的全部内容,希望可以抛砖引玉,一起学习,共同进步。学习编程,从关注【老码识途】开始!!!