软件License设计

发布时间 2023-05-17 10:07:21作者: DoubleLi

如何保护软件版权,最常用的办法就是设计一套license验证框架。

1、我们的常规需求如下:

复制代码
1.可以限制软件只能在一台机器上使用;

目前很多软件都是一机一码的销售,软件换一台机器则不能使用,想要几台机器使用就得购买几个license;
2.可以设置一个使用期限;

试用版软件一般有几十天的免费使用期,销售时也可以分为一年版、终生版等;

3.可以设置能使用的权限;

试用版软件对处理能力有限制,比如短信发送软件设置发送条数限制,抽奖软件设置总人数限制,打印软件试用版插一个软件广告等等;
复制代码

进一步分析如下:

试用版:无需License,安装后的默认版本;有使用期限;有功能限制或插入广告等;

有限期限版:需要License;有使用期限;无功能限制;

终身免费版:需要License;无限制;

一般破解的办法有以下几种:

1.试用版到期后修改系统时间;

2.试用版到期后找到license文件并修改或删除;

3.试用版到期后卸载软件,重新安装;

4.黑客直接反编译软件,屏蔽掉验证License的逻辑;

 

2、License结构设计

针对以上需求,我们来对应设计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。

3、会使用到的一些工具类

生成客户机器码的工具类:

 View Code

说明:上面的HardwareInfo类就是帮助生成机器唯一信息的。实际运用中,mac地址、声卡网卡等容易变动,可以不加到信息里面。

对象序列化帮助工具:

 View Code

以及加解密工具:EncodeHelper    源码见我的另一篇文章:

常见加密算法简析

 注册表操作类:
 View Code

注册表操作需要用到管理员权限,否则会提示无权限操作注册表。解决办法是在项目中添加“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如下:

           

Demo下载

做好了License架构,是不是软件版权保护就完成了呢?答案是否定的。现在我们已经造了银行保险柜的门,却只有篱笆一样容易攻破的墙,黑客直接反编译你的软件,去掉权限验证的逻辑再重新编译,就可以终身永久免费使用你的劳动成果,所以不加密的软件在黑客眼里就像在裸奔。混淆、加壳是另一个更加复杂的技术,下面介绍几款混淆工具。

4.混淆工具介绍:

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混淆器。

使用教程:https://www.cnblogs.com/tuyile006/p/8461326.html

Dotfuscator:

这款大家应该比较熟悉了,官方自带的。

使用教程:https://www.cnblogs.com/tuyile006/p/9183925.html