Skip to content

Instantly share code, notes, and snippets.

@rqx110
Created March 9, 2018 08:52
Show Gist options
  • Save rqx110/fab27f12988cac3c31da7fd8c10cca46 to your computer and use it in GitHub Desktop.
Save rqx110/fab27f12988cac3c31da7fd8c10cca46 to your computer and use it in GitHub Desktop.
软件注册序列号设计
public class LicenseInfo
{
public LicenseInfo()
{
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; }
}
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;//使用期限受限
}
//保存至注册表等
MediaTypeNames.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();
}
}
internal enum LicenseOffset
{
Left,
Right
}
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;
}
}
@rqx110
Copy link
Author

rqx110 commented Mar 9, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment