C# 软件Licence应用实例

简介: 我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。

我们在使用一些需要购买版权的软件产品时,或者我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件。简单的是一串序列码或者一个许可证文件,复杂的是一个定制化插件包。于是有的小伙伴就开始好奇这个许可是怎么实现的,特别是在离线情况下它是怎么给软件授权,同时又能避免被破解的。

 

License应用场景

 

本文主要介绍的是许可证形式的授权。

 

1. 如何控制只在指定设备上使用

 

如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护,每个设备都有唯一标识:mac地址,ip地址,主板序列号等,在许可证中指定唯一标识则只能指定设备使用。

 

2. 如何控制软件使用期限

 

为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。

 

Licence实现方案

 

1. 流程设计

 

  • 形式:许可证以文件形式下发,放在客户端电脑指定位置
  • 内容:以上控制内容以dom节点形式放在文件中
  • 流程:将控制项加密后写入license文件节点,部署到客户机器,客户机使用时再读取license文件内容与客户机实际参数进行匹配校验

 

2. 文件防破解

 

  • 防止篡改:文件内容加密,使用AES加密,但是AES加密解密都是使用同一个key;使用非对称公私钥(本文使用的RSA)对内容加密解密,但是对内容长度有限制;综合方案,将AES的key(内部定义)用RSA加密,公钥放在加密工具中,内部持有,私钥放在解密工具中,引入软件产品解密使用。
  • 防止修改系统时间绕过许可证使用时间:许可证带上发布时间戳,并定时修改运行时间记录到文件,如果系统时间小于这个时间戳,就算大于许可证限制的起始时间也无法使用
  • 提高破解难度:懂技术的可以将代码反编译过来修改代码文件直接绕过校验,所以需要进行代码混淆,有测试过xjar的混淆效果比较好。

 

Licence验证流程图

 

关于Licence验证软件合法性流程图,如下所示:

 

核心源码

 

本实例主要讲解Licence的实际验证过程,分为三部分:

  1. 测试客户端【LicenceTest】,主要用于模拟客户端验证Licence的过程。
  2. 生成工具【LicenceTool】,主要用于根据客户生成的电脑文件,生成对应的Licence。
  3. 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应用实例的全部内容,希望可以抛砖引玉,一起学习,共同进步。学习编程,从关注【老码识途】开始!!!

相关文章
|
6天前
|
数据采集 API 开发工具
Baumer工业相机堡盟工业相机如何通过NEOAPISDK设置软件触发模式(C#)
Baumer工业相机堡盟工业相机如何通过NEOAPISDK设置软件触发模式(C#)
43 1
|
6天前
|
监控 安全 C#
开发公司电脑监控软件的报警系统:一个C#示例
在当今数字化时代,企业对其计算机网络和系统的安全性和稳定性越来越重视。为了确保员工遵守公司政策、保护机密信息以及监控系统的正常运行,开发一种可靠的公司电脑监控软件变得至关重要。本文将介绍如何使用C#编写一个简单而有效的报警系统,以便监控关键数据并在必要时发出警报。
14 0
|
6天前
|
存储 运维 BI
基于C#-VC-MSSQL开发的全套PACS系统源码 3D PACS系统源码:可实现医学影像获取、存档、观片、处理、打印多项应用
PACS的功能价值在于通过连接不同的影像设备,存储与管理图像,图像的调用与后处理,实现资源共享,降低成本,达到提高工作效率、提升医疗水平的目地;
15 1
基于C#-VC-MSSQL开发的全套PACS系统源码  3D PACS系统源码:可实现医学影像获取、存档、观片、处理、打印多项应用
|
6天前
|
SQL 开发框架 .NET
C#linq表达式的应用
C#linq表达式的应用
9 0
|
6天前
|
存储 数据采集 API
C# GetField 方法应用实例
C# GetField 方法应用实例
|
6天前
|
JSON 安全 API
C# GetMethod 方法应用实例
C# GetMethod 方法应用实例
|
6天前
|
开发框架 Linux C#
C#编程的跨平台应用
【4月更文挑战第21天】C#与.NET Core的结合使得跨平台应用开发变得高效便捷,提供统一编程模型和高性能。丰富的类库、活跃的社区支持及Visual Studio Code、Xamarin等工具强化了其优势。广泛应用在企业系统、云服务和游戏开发中,虽面临挑战,但随着技术进步,C#在跨平台开发领域的前景广阔。
|
6天前
|
C#
手把手教你用C#写一个刷屏软件
手把手教你用C#写一个刷屏软件
|
6天前
|
程序员 C#
C# 面向对象编程进阶:构造函数详解与访问修饰符应用
构造函数是一种特殊的方法,用于初始化对象。构造函数的优势在于,在创建类的对象时调用它。它可以用于为字段设置初始值
47 1
|
6天前
|
C#
C# 运算符详解:包含算术、赋值、比较、逻辑运算符及 Math 类应用
运算符用于对变量和值执行操作。在C#中,有多种运算符可用,包括算术运算符、关系运算符、逻辑运算符等。
51 1