[连载]《C#通讯(串口和网络)框架的设计与实现》- 14.序列号的设计,不重复的实现一机一码
目 录
第十四章 序列号的设计... 2
14.1 设计原则... 2
14.2 设计思想... 3
14.3 代码实现... 4
14.4 代码混淆... 18
14.5 代码破解... 18
14.6 小结... 18
第十四章 序列号的设计
序列号作为软件使用授权方式之一,被广泛使用在应用软件方面。主要考虑到这几方面:1.对知识产权的保护,毕竟付出来脑力劳动和体力劳动。2.商业竞争中增加防守的能力,防止被竞争对手盗取。3.增强合同的执行效力,防止另一方由于各种原因破坏合作机制。
基于上述方面,从保护、防守思维模式角度考虑,增加序列号功能是有必要的。每个作者或公司设计序列号的方式不一样,就是因为不一样,所以才能达到我们增加该功能的效果。
14.1 设计原则
- 序列号长度尽可能短
主要是从成本角度考虑的。例如用户现场需要一个正版软件的序列号,你把序列号信息通过什么方式传递给用户呢?假设我们用对称或非对称方式生成一个很长的序列号,如果口述告诉对方的话,那么对方肯定要用纸和笔进行记录,最后输入到软件后还不一定正确;如果把序列号以文件的方式通过网络传递给对方,那么需要占用网络资源,另外对方的电脑不一定有网络环境。不管如何,很长的序列号在生成和传递的过程中可能涉及到的成本包括:材料成本、流量成本、人力成本和时间成本等。
如果一个字符可以表达序列号所需要的完整信息,那么是最理想的。但是,这是理想状态,是不可能实现的,至少以我现在的能力是无法完成的。所以,要以最佳的长度表达出序列号的全部信息。
- 避免出现容易混淆的字符生成一个序列号发给了用户,这个序列号包括:数字0和字母O,数字1和字母l。难道让用户一遍一遍的试嘛,这样的用户体验太差了,虽然嘴上不说出来,至少感觉不太舒服。
14.2 设计思想
设计的思想要看序列号要实现什么样的功能和具备什么属性。从功能角度考虑,包括:1.一个计算机一个序列号;2.尽管输入的条件都一样,每次生成的序列号都不一样;3.对使用的时限进行验证;4.序列号有注册时限,超过规定的使用时间,序列号作废,避免短时间多次注册。从属性角度考虑,包括:同样的计算机、同样的输入条件生成的序列号都不一样。
我们把上述因素考虑进去,序列号长度为25位字符,序列号生成格式和元素信息如下图:
X01-X05:为计算机的特征码,5位字符串,获得机器某个部件的ID,这个部件可能为CPU、网卡、硬盘等信息,把ID进行MD5加密后取前5个字符作为特征码,来实现一机一码。这种方式,特征码有可能有相同的情况,但是机率很小。
X06-X13:为生成序列号的日期,8位字符串,格式为:yyyyMMdd。与后边的使用时间限制配合使用,来验证软件的使用期限。
X14-X15:为注册时间限制,2位数字字符,从生成序列号日期算起,超过此注册时间限制,序列号将无法正常进行注册操作。
X16-X20:为使用时间限制,5位数字字符,与生成序列号日期配合使用来验证软件使用期限。
X21:为序列号的偏移量,1位字符,不管在什么场景下,每次生成序列号的偏移量都不一样。
X22-X25:为保留数据位,暂时不使用。自定义一个序列号字典信息,例如:_Dictionary ="JCB8EF2GH7K6MVP9QR3TXWY4",把容易混淆的字符去掉,这个可以自定义。序列号的每个部分都是通过随机生成的偏移量(X21),对字典进行位移,根据输入的数字信息对应字典的下标提取相应的字符作为序列号的一个字符。
生成序列号的大概过程:
- 在字典信息的长度范围内随机生成一个偏移量数字。
- 根据偏移量数字对字典进行左或右的循环移动。
- 根据输入的数字信息,例如:2015中的2,作为下标,从字典信息中提取出相应的字符。
反向解析大概过程类似,只需要根据X21字符,与字典的字符进行匹配,对应的下标作为偏移量,就可以反向解析出各项信息。
14.3 代码实现
1.MD5操作类:
public class Safety
{
public static string MD5(string str)
{
string strResult = "";
MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] bData = md5.ComputeHash(Encoding.Unicode.GetBytes(str));
for (int i = 0; i < bData.Length; i++)
{
strResult = strResult + bData[i].ToString("X");
}
return strResult;
}
}
2.注册信息类:
public class RegInfo
{
public RegInfo()
{
KeySn = "";
Date=DateTime.MinValue;
RegLimitDays = 0;
UseLimitDays = 0;
Offset = 0;
}
public string KeySn { get; set; }
public DateTime Date { get; set; }
public int RegLimitDays { get; set; }
public int UseLimitDays { get; set; }
public int Offset { get; set; }
}
3.偏移操作类型:
internal enum OffsetType
{
Left,
Right
}
4. 序列号管理类
public class LicenseManage
{
/// <summary>
/// 序列号字典,把数字和字母容易混淆的字符去掉。所产生的25位序列号从这个字典中产生。
/// </summary>
private static string _Dictionary = "JCB8EF2GH7K6MVP9QR3TXWY4"; /// <summary>
/// 可以自定义字典字符串
/// </summary>
public static string Dictionary
{
get { return _Dictionary; }
set
{
if (value.Length < 9)
{
throw new ArgumentOutOfRangeException("设置的字典长度不能小于9个字符");
} _Dictionary = value;
}
} /// <summary>
/// 生成序列号
/// </summary>
/// <param name="key">关键字,一般为CPU号、硬盘号、网卡号,用于与序列号绑定,实现一机一码</param>
/// <param name="now">现在的时间</param>
/// <param name="regLimitDays">注册天数限制,超过此天数,再进行注册,序列号就失效了,不能再使用了</param>
/// <param name="useLimitDays">使用天数限制,超过此天数,可以设置软件停止运行等操作</param>
/// <returns>返回序列号,例如:xxxxx-xxxxx-xxxxx-xxxxx-xxxxx</returns>
public static string BuildSn(string key, DateTime now, int regLimitDays, int useLimitDays)
{
if (regLimitDays < 0 || regLimitDays > 9)
{
throw new ArgumentOutOfRangeException("注册天数限制范围为0-9");
} if (useLimitDays < 0 || useLimitDays > 99999)
{
throw new ArgumentOutOfRangeException("使用天数限制范围为0-99999");
} /*
*关键字用MD5加密后,取后5个字符作为序列号第1组字符
*/
string md5 = Safety.MD5(key);
string x1 = md5.Substring(md5.Length - 5); /*
* 生成随机偏移量
*/
Random rand = new Random();
int offset = rand.Next(1, Dictionary.Length - 1); /*
* 第5组的第1个字符保存偏移量字符,其余4个字符随机生成,作为保留位
*/
string x5 = Dictionary[offset].ToString();
for (int i = 0; i < 4; i++)
{
x5 += Dictionary[rand.Next(0, Dictionary.Length - 1)].ToString();
} /*
* 以注册时间(yyyyMMdd)和注册时间限制生成第2组和第3组序列号,一共10位字符串
*/
string dateSn = GetDateSn(now, offset);
string regLimitSn = GetRegLimitSn(regLimitDays, offset);
string x2 = dateSn.Substring(0, 5);
string x3 = dateSn.Substring(dateSn.Length - 3) + regLimitSn; /*
*以使用时间限制生成第4组序列号,一共5位字符串
*/
string x4 = GetUseLimitSn(useLimitDays, offset); return String.Format("{0}-{1}-{2}-{3}-{4}", x1, x2, x3, x4, x5);
} /// <summary>
/// 注册序列号
/// </summary>
/// <param name="key">关键字,一般为CPU号、硬盘号、网卡号,用于与序列号绑定,实现一机一码</param>
/// <param name="sn">序列号</param>
/// <param name="desc">描述信息</param>
/// <returns>注册状态,成功:0</returns>
internal static int RegSn(string key, string sn, ref string desc)
{
if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(sn))
{
throw new ArgumentNullException("参数为空");
} LicenseInfo regInfo = GetRegInfo(sn);
string md5 = Safety.MD5(key);
if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0)
{
desc = "关键字与序列号不匹配";
return -1;//关键字与序列号不匹配
} if (regInfo.Date == DateTime.MaxValue || regInfo.Date == DateTime.MinValue || regInfo.Date > DateTime.Now.Date)
{
desc = "序列号时间有错误";
return -2;//序列号时间有错误
} TimeSpan ts = DateTime.Now.Date - regInfo.Date;
if (ts.TotalDays > 9 || ts.TotalDays < 0)
{
desc = "序列号失效";
return -3;//序列号失效
} if (regInfo.UseLimitDays <= 0)
{
desc = "使用期限受限";
return -4;//使用期限受限
} Application.UserAppDataRegistry.SetValue("SN", sn); desc = "注册成功";
return 0;
} /// <summary>
/// 检测序列号,试用于时钟定时调用
/// </summary>
/// <param name="key">关键字,一般为CPU号、硬盘号、网卡号,用于与序列号绑定,实现一机一码</param>
/// <param name="desc">描述信息</param>
/// <returns>检测状态,成功:0</returns>
internal static int CheckSn(string key, ref string desc)
{
if (String.IsNullOrEmpty(key))
{
throw new ArgumentNullException("参数为空");
} object val = Application.UserAppDataRegistry.GetValue("SN"); if (val == null)
{
desc = "未检测到本机的序列号";
return -1;
} string sn = val.ToString(); LicenseInfo regInfo = GetRegInfo(sn);
string md5 = Safety.MD5(key);
if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0)
{
desc = "关键字与序列号不匹配";
return -2;//关键字与序列号不匹配
} if ((DateTime.Now.Date - regInfo.Date).TotalDays > regInfo.UseLimitDays)
{
desc = "序列使用到期";
return -3;//关键字与序列号不匹配
} desc = "序列号可用";
return 0;
} /// <summary>
/// 获得剩余天数
/// </summary>
/// <param name="key">关键字,一般为CPU号、硬盘号、网卡号,用于与序列号绑定,实现一机一码</param>
/// <returns>剩余天数</returns>
internal static int GetRemainDays(string key)
{
if (String.IsNullOrEmpty(key))
{
throw new ArgumentNullException("参数为空");
} object val = Application.UserAppDataRegistry.GetValue("SN"); if (val == null)
{
return 0;
} string sn = val.ToString(); LicenseInfo regInfo = GetRegInfo(sn);
string md5 = Safety.MD5(key);
if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0)
{
return 0;//关键字与序列号不匹配,不能使用。
} //<=0的情况,证明不可以使用。
return regInfo.UseLimitDays - (int)(DateTime.Now.Date - regInfo.Date).TotalDays;
} /// <summary>
/// 根据序列号,反推注册信息
/// </summary>
/// <param name="sn">序列号</param>
/// <returns>注册信息</returns>
private static LicenseInfo GetRegInfo(string sn)
{
LicenseInfo reg = new LicenseInfo();
string[] splitSn = sn.Split('-');
if (splitSn.Length != 5)
{
throw new FormatException("序列号格式错误,应该带有'-'字符");
} reg.KeySn = splitSn[0];
reg.Offset = Dictionary.IndexOf(splitSn[4][0]);
reg.Date = GetDate(splitSn[1] + splitSn[2].Substring(0, 3), reg.Offset);
reg.RegLimitDays = GetRegLimitDays(splitSn[2].Substring(3, 2), reg.Offset);
reg.UseLimitDays = GetUseLimitDays(splitSn[3], reg.Offset);
return reg;
} /// <summary>
/// 以当前时间和偏移量生成当前时间对应的字符串
/// </summary>
/// <param name="now">当前时间</param>
/// <param name="offset">偏移量</param>
/// <returns>返回日期对应的字符串,8位字符串</returns>
private static string GetDateSn(DateTime now, int offset)
{
string dateSn = "";
string date = now.ToString("yyyyMMdd");
string newDic = Dictionary;
for (int i = 0; i < date.Length; i++)
{
newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
int num = int.Parse(date[i].ToString());
dateSn += newDic[num].ToString();
}
return dateSn;
} /// <summary>
/// 根据注册时间序列号反推注册时间
/// </summary>
/// <param name="dateSn">时间字符串</param>
/// <param name="offset">偏移量</param>
/// <returns>时间</returns>
private static DateTime GetDate(string dateSn, int offset)
{
string dateStr = "";
string newDic = Dictionary;
for (int i = 0; i < dateSn.Length; i++)
{
newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
int num = newDic.IndexOf(dateSn[i]);
dateStr += num;
}
return new DateTime(int.Parse(dateStr.Substring(0, 4)), int.Parse(dateStr.Substring(4, 2)), int.Parse(dateStr.Substring(6, 2)));
} /// <summary>
/// 以注册时间限制和偏移量生成对应的字符串
/// </summary>
/// <param name="regLimitDays"></param>
/// <param name="offset"></param>
/// <returns>返回对应的注册时间限制的字符串,2位字符串</returns>
private static string GetRegLimitSn(int regLimitDays, int offset)
{
string regLimitSn = "";
string regLimitStr = regLimitDays.ToString("00");
string newDic = Dictionary;
for (int i = 0; i < regLimitStr.Length; i++)
{
newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
int num = int.Parse(regLimitStr[i].ToString());
regLimitSn += newDic[num].ToString();
}
return regLimitSn;
} /// <summary>
/// 根据注册时间限制字符串反推注册时间限制
/// </summary>
/// <param name="regLimitSn">注册时间限制字符串</param>
/// <param name="offset">偏移量</param>
/// <returns>注册时间限制</returns>
private static int GetRegLimitDays(string regLimitSn, int offset)
{
string regLimitStr = "";
string newDic = Dictionary;
for (int i = 0; i < regLimitSn.Length; i++)
{
newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
int num = newDic.IndexOf(regLimitSn[i]);
regLimitStr += num;
}
return int.Parse(regLimitStr);
} /// <summary>
/// 以使用时间限制和偏移量生成对应的字符串
/// </summary>
/// <param name="useLimitDays">使用时间限制</param>
/// <param name="offset">偏移量</param>
/// <returns>使用时间限制对应字符串,5位字符串</returns>
private static string GetUseLimitSn(int useLimitDays, int offset)
{
string useLimitSn = "";
string useLimitStr = useLimitDays.ToString("00000");
string newDic = Dictionary;
for (int i = 0; i < useLimitStr.Length; i++)
{
newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
int num = int.Parse(useLimitStr[i].ToString());
useLimitSn += newDic[num].ToString();
}
return useLimitSn;
} /// <summary>
/// 根据使用时间限制字符串反推使用时间限制
/// </summary>
/// <param name="regLimitSn">使用时间限制字符串</param>
/// <param name="offset">偏移量</param>
/// <returns>使用时间限制</returns>
private static int GetUseLimitDays(string useLimitSn, int offset)
{
string useLimitStr = "";
string newDic = Dictionary;
for (int i = 0; i < useLimitSn.Length; i++)
{
newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
int num = newDic.IndexOf(useLimitSn[i]);
useLimitStr += num;
}
return int.Parse(useLimitStr);
} /// <summary>
/// 根据字典、偏移量和偏移类型生成新的字典
/// </summary>
/// <param name="dic"></param>
/// <param name="offset"></param>
/// <param name="offsetType"></param>
/// <returns></returns>
private static string GetNewDictionaryString(string dic, int offset, LicenseOffset offsetType)
{
StringBuilder sb = new StringBuilder(dic);
if (offsetType == LicenseOffset.Left)
{
for (int i = 0; i < offset; i++)
{
string head = sb[0].ToString();
sb.Remove(0, 1);
sb.Append(head);
}
}
else if (offsetType == LicenseOffset.Right)
{
for (int i = 0; i < offset; i++)
{
string end = sb[dic.Length - 1].ToString();
sb.Remove(dic.Length - 1, 1);
sb.Insert(0, end);
}
}
return sb.ToString();
}
}
14.4 代码混淆
从安全角度来讲,.NET程序如果不加混淆的话,很容易被反编译出源代码的。从专业角度来讲,即使增加了序列号功能,也无济于事,专业的人员分分钟可以破解掉,尽管这样干的人很少,但是存在这种可能性。如果一个软件人员想了解一个很好的软件,第一反映可能就是反编译。
对于公司或商业使用的软件来讲,增加混淆还是有必要的,尽管现在开源很流行。
14.5 代码破解
不管.NET程序如何进行混淆,理论上都是可以破解的,理论的东西就不赘述了。通常接触过的破解方式有两种:注册机方式和暴力方式。
注册机的方式,需要通过软件的验证序列号的过程和机制反向推算出序列号的生成算法,根据反推的算法开发一个小软件,用于生成脱离作者授权生成序列号。这种方式不会破坏程序本身的代码,相对温和。暴力的方式,就是找到序列号验证部分的代码,通过删除或绕过验证代码等方式不让代码有效执行。这种方式会对程序本身的代码进行改动,所以也存在一些风险。
14.6 小结
实现序列号有多种方式,上述方式不一定最好,但是希望对开发者有一定帮助。
最终实现效果图如下:
作者:唯笑志在
Email:504547114@qq.com
QQ:504547114
.NET开发技术联盟:54256083
文档下载:http://pan.baidu.com/s/1pJ7lZWf
官方网址:http://www.bmpj.net
[连载]《C#通讯(串口和网络)框架的设计与实现》- 14.序列号的设计,不重复的实现一机一码的更多相关文章
- [连载]《C#通讯(串口和网络)框架的设计与实现》- 0.前言
目 录 前言 前言 刚参加工作,使用过VB.VC开发软件,随着C#的崛起,听说是C++++,公司决定以后开发软件使用C#,凭借在 ...
- 谷歌Volley网络框架讲解——第一篇
自从公司新招了几个android工程师后,我清闲了些许.于是就可以有时间写写博客,研究一些没来的研究的东西. 今年的谷歌IO大会上,谷歌推出了自己的网络框架——Volley.不久前就听说了但是没有cl ...
- 《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT
1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》-1.通讯框架介绍
[连载]<C#通讯(串口和网络)框架的设计与实现>- 0.前言 目 录 第一章 通讯框架介绍... 2 1.1 通讯的本质... 2 1 ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》- 6.通讯控制器的设计
目 录 第六章 通讯控制器的设计... 2 6.1 控制器接口... 2 6.2 串口控制器... 3 6.3 ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》-2.框架的总体设计
目 录 C#通讯(串口和网络)框架的设计与实现... 1 (SuperIO)- 框架的总体设计... 1 第二章 框架总体的设计... 2 2.1 ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》- 5.串口和网络统一IO设计
目 录 第五章 串口和网络统一IO设计... 2 5.1 统一IO接口... 2 5.1.1 串口IO.. 4 5.1.2 网络IO.. ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》-4.设备驱动管理器的设计
目 录 第四章 设备驱动管理器的设计... 2 4.1 接口定义... 2 4.2 设备容器... 7 4.3 ...
- [连载]《C#通讯(串口和网络)框架的设计与实现》-3.设备驱动的设计
目 录 第三章 设备驱动的设计... 2 3.1 初始化设备... 4 3.2 运行设备接口设计... 4 3.3 ...
随机推荐
- springmvc 多数据源 SSM java redis shiro ehcache 头像裁剪
获取下载地址 QQ 313596790 A 调用摄像头拍照,自定义裁剪编辑头像 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,开发利器)+快速构建表单; 技术:31359679 ...
- APP多版本共存,服务端如何兼容?
做过APP产品的技术人员都知道,APP应用属于一种C/S架构的,所以在做多版本兼容,升级等处理则比较麻烦,不像web应用那么容易.下面将带大家分析几种常见的情况和应对方式: 小改动或者新加功能的 这种 ...
- 去IOE的一点反对意见以及其他
某天在机场听见两老板在聊天,说到他们目前销售的报表老跟不上的问题,说要请一个人,专门合并和分析一些发过来的excel表格,我真想冲上去说,老板,你需要的是一个信息处理的系统,你需要咨询么.回来一直耿耿 ...
- __Block与__Weak区别
一.__block理解: Blocks可以访问局部变量,但是不能修改, 声明block的时候实际上是把当时的临时变量又复制了一份, 在block里即使修改了这些复制的变量,也不影响外面的原始变量.即所 ...
- LVM基本介绍与常用命令
一.LVM介绍LVM是 Logical Volume Manager(逻辑卷管理)的简写,它是Linux环境下对磁盘分区进行管理的一种机制LVM - 优点:LVM通常用于装备大量磁盘的系统,但它同样适 ...
- linux下配置matlab运行环境(MCR)
在安装好的matlab下有MCR(MatlabCompilerRuntime)在matlab2011/toolbox/compiler/deploy/glnxa64下找到MCRInstaller.zi ...
- Linux初识
在这篇文章中你讲看到如下内容: 计算机的组成及功能: Linux发行版之间的区别和联系: Linux发行版的基础目录及功用规定: Linux系统设计的哲学思想: Linux系统上获取命令帮助,及man ...
- 开源发布:VS代码段快捷方式及可视化调试快速部署工具
前言: 很久前,我发过两篇文章,分别介绍自定义代码版和可视化调试: 1:Visual Studio 小技巧:自定义代码片断 2:自定义可视化调试工具(Microsoft.VisualStudio.De ...
- 我写的一些前端开源项目(均托管到github)
大部分项目都是平时项目用到的某些功能,觉得有趣或者复用性有点高就提取成一个单独项目来做维护 coffee-tmpl : 一个极简的模板引擎和ejs及underscore的template类似 turn ...
- ASP.NET Web API Model-ModelBinder
ASP.NET Web API Model-ModelBinder 前言 本篇中会为大家介绍在ASP.NET Web API中ModelBinder的绑定原理以及涉及到的一些对象模型,还有简单的Mod ...