目       录

第十四章     序列号的设计... 2

14.1        设计原则... 2

14.2        设计思想... 3

14.3        代码实现... 4

14.4        代码混淆... 18

14.5        代码破解... 18

14.6        小结... 18

第十四章      序列号的设计

序列号作为软件使用授权方式之一,被广泛使用在应用软件方面。主要考虑到这几方面:1.对知识产权的保护,毕竟付出来脑力劳动和体力劳动。2.商业竞争中增加防守的能力,防止被竞争对手盗取。3.增强合同的执行效力,防止另一方由于各种原因破坏合作机制。

基于上述方面,从保护、防守思维模式角度考虑,增加序列号功能是有必要的。每个作者或公司设计序列号的方式不一样,就是因为不一样,所以才能达到我们增加该功能的效果。

14.1     设计原则

  1. 序列号长度尽可能短

主要是从成本角度考虑的。例如用户现场需要一个正版软件的序列号,你把序列号信息通过什么方式传递给用户呢?假设我们用对称或非对称方式生成一个很长的序列号,如果口述告诉对方的话,那么对方肯定要用纸和笔进行记录,最后输入到软件后还不一定正确;如果把序列号以文件的方式通过网络传递给对方,那么需要占用网络资源,另外对方的电脑不一定有网络环境。不管如何,很长的序列号在生成和传递的过程中可能涉及到的成本包括:材料成本、流量成本、人力成本和时间成本等。

如果一个字符可以表达序列号所需要的完整信息,那么是最理想的。但是,这是理想状态,是不可能实现的,至少以我现在的能力是无法完成的。所以,要以最佳的长度表达出序列号的全部信息。

  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),对字典进行位移,根据输入的数字信息对应字典的下标提取相应的字符作为序列号的一个字符。

生成序列号的大概过程:

  1. 在字典信息的长度范围内随机生成一个偏移量数字。
  2. 根据偏移量数字对字典进行左或右的循环移动。
  3. 根据输入的数字信息,例如: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.序列号的设计,不重复的实现一机一码的更多相关文章

  1. [连载]《C#通讯(串口和网络)框架的设计与实现》- 0.前言

                                  目       录 前言 前言 刚参加工作,使用过VB.VC开发软件,随着C#的崛起,听说是C++++,公司决定以后开发软件使用C#,凭借在 ...

  2. 谷歌Volley网络框架讲解——第一篇

    自从公司新招了几个android工程师后,我清闲了些许.于是就可以有时间写写博客,研究一些没来的研究的东西. 今年的谷歌IO大会上,谷歌推出了自己的网络框架——Volley.不久前就听说了但是没有cl ...

  3. 《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  4. [连载]《C#通讯(串口和网络)框架的设计与实现》-1.通讯框架介绍

    [连载]<C#通讯(串口和网络)框架的设计与实现>- 0.前言 目       录 第一章           通讯框架介绍... 2 1.1           通讯的本质... 2 1 ...

  5. [连载]《C#通讯(串口和网络)框架的设计与实现》- 6.通讯控制器的设计

    目       录 第六章           通讯控制器的设计... 2 6.1           控制器接口... 2 6.2           串口控制器... 3 6.3          ...

  6. [连载]《C#通讯(串口和网络)框架的设计与实现》-2.框架的总体设计

    目       录 C#通讯(串口和网络)框架的设计与实现... 1 (SuperIO)- 框架的总体设计... 1 第二章           框架总体的设计... 2 2.1           ...

  7. [连载]《C#通讯(串口和网络)框架的设计与实现》- 5.串口和网络统一IO设计

    目       录 第五章           串口和网络统一IO设计... 2 5.1           统一IO接口... 2 5.1.1    串口IO.. 4 5.1.2    网络IO.. ...

  8. [连载]《C#通讯(串口和网络)框架的设计与实现》-4.设备驱动管理器的设计

    目       录 第四章           设备驱动管理器的设计... 2 4.1           接口定义... 2 4.2           设备容器... 7 4.3          ...

  9. [连载]《C#通讯(串口和网络)框架的设计与实现》-3.设备驱动的设计

    目       录 第三章           设备驱动的设计... 2 3.1           初始化设备... 4 3.2           运行设备接口设计... 4 3.3        ...

随机推荐

  1. 浅谈Slick(2)- Slick101:第一个动手尝试的项目

    看完Slick官方网站上关于Slick3.1.1技术文档后决定开始动手建一个项目来尝试一下Slick功能的具体使用方法.我把这个过程中的一些了解和想法记录下来和大家一起分享.首先我用IntelliJ- ...

  2. RMS Server打开或关闭日志记录

    原文: https://technet.microsoft.com/zh-cn/library/cc732758 在 Active Directory Rights Management Servic ...

  3. Android 算法 关于递归和二分法的小算法

     // 1. 实现一个函数,在一个有序整型数组中二分查找出指定的值,找到则返回该值的位置,找不到返回 -1. package demo; public class Mytest { public st ...

  4. 敏捷软件开发VS传统软件工程

    敏捷软件开发:又称敏捷开发,是一种从1990年代开始逐渐引起广泛关注的一些新兴软件开发方法,是一种应对快速变化的需求的一种软件开发能力. 与传统软件工程相比,它们的具体名称.理念.过程.术语都不尽相同 ...

  5. TFS 2015 敏捷开发实践 – 在Kanban上运行一个Sprint

    前言:在 上一篇 TFS2015敏捷开发实践 中,我们给大家介绍了TFS2015中看板的基本使用和功能,这一篇中我们来看一个具体的场景,如何使用看板来运行一个sprint.Sprint是Scrum对迭 ...

  6. python安装BeautifulSoup注意事项

    好久没有写爬虫了,最近用Python的BeautifulSoup4.Scrapy分别对以前写的spider进行优化,发现python3.5后这些库变化了很多,遇到了许多问题,在这里做一下总结. 切换环 ...

  7. 【每日一linux命令2】命令执行顺序:

    二.命令顺序:     若在 shell 内置的命令/bin 以及/usr/bin 之下都出现了命令 pwd,那当我们执行该命令时,会执行哪 一个?答案是第一优先执行 shell 内置的命令,再执行路 ...

  8. 模拟实现Spring中的注解装配

    本文原创,地址为http://www.cnblogs.com/fengzheng/p/5037359.html 在Spring中,XML文件中的bean配置是实现Spring IOC的核心配置文件,在 ...

  9. Azure 上通过 SendGrid 发送邮件

    SendGrid 是什么? SendGrid 是架构在云端的电子邮件服务,它能提供基于事务的可靠的电子邮件传递. 并且具有可扩充性和实时分析的能力.常见的用例有: 自动回复用户的邮件 定期发送信息给用 ...

  10. GooglePlay 首页效果----tab的揭示效果(Reveal Effect) (1)

    GooglePlay 首页效果----tab的揭示效果(Reveal Effect) (1) 前言: 无意打开GooglePlay app来着,然后发现首页用了揭示效果,连起来用着感觉还不错. 不清楚 ...