目 錄
第十四章 序列號的設(shè)計(jì)... 2
14.1 設(shè)計(jì)原則... 2
14.2 設(shè)計(jì)思想... 3
14.3 代碼實(shí)現(xiàn)... 4
14.4 代碼混淆... 18
14.5 代碼破解... 18
14.6 小結(jié)... 18
序列號作為軟件使用授權(quán)方式之一,被廣泛使用在應(yīng)用軟件方面。主要考慮到這幾方面:1.對知識產(chǎn)權(quán)的保護(hù),畢竟付出來腦力勞動和體力勞動。2.商業(yè)競爭中增加防守的能力,防止被競爭對手盜取。3.增強(qiáng)合同的執(zhí)行效力,防止另一方由于各種原因破壞合作機(jī)制。
基于上述方面,從保護(hù)、防守思維模式角度考慮,增加序列號功能是有必要的。每個作者或公司設(shè)計(jì)序列號的方式不一樣,就是因?yàn)椴灰粯樱圆拍苓_(dá)到我們增加該功能的效果。
主要是從成本角度考慮的。例如用戶現(xiàn)場需要一個正版軟件的序列號,你把序列號信息通過什么方式傳遞給用戶呢?假設(shè)我們用對稱或非對稱方式生成一個很長的序列號,如果口述告訴對方的話,那么對方肯定要用紙和筆進(jìn)行記錄,最后輸入到軟件后還不一定正確;如果把序列號以文件的方式通過網(wǎng)絡(luò)傳遞給對方,那么需要占用網(wǎng)絡(luò)資源,另外對方的電腦不一定有網(wǎng)絡(luò)環(huán)境。不管如何,很長的序列號在生成和傳遞的過程中可能涉及到的成本包括:材料成本、流量成本、人力成本和時(shí)間成本等。
如果一個字符可以表達(dá)序列號所需要的完整信息,那么是最理想的。但是,這是理想狀態(tài),是不可能實(shí)現(xiàn)的,至少以我現(xiàn)在的能力是無法完成的。所以,要以最佳的長度表達(dá)出序列號的全部信息。
設(shè)計(jì)的思想要看序列號要實(shí)現(xiàn)什么樣的功能和具備什么屬性。從功能角度考慮,包括:1.一個計(jì)算機(jī)一個序列號;2.盡管輸入的條件都一樣,每次生成的序列號都不一樣;3.對使用的時(shí)限進(jìn)行驗(yàn)證;4.序列號有注冊時(shí)限,超過規(guī)定的使用時(shí)間,序列號作廢,避免短時(shí)間多次注冊。從屬性角度考慮,包括:同樣的計(jì)算機(jī)、同樣的輸入條件生成的序列號都不一樣。
我們把上述因素考慮進(jìn)去,序列號長度為25位字符,序列號生成格式和元素信息如下圖:
X01-X05:為計(jì)算機(jī)的特征碼,5位字符串,獲得機(jī)器某個部件的ID,這個部件可能為CPU、網(wǎng)卡、硬盤等信息,把ID進(jìn)行md5加密后取前5個字符作為特征碼,來實(shí)現(xiàn)一機(jī)一碼。這種方式,特征碼有可能有相同的情況,但是機(jī)率很小。
X06-X13:為生成序列號的日期,8位字符串,格式為:yyyyMMdd。與后邊的使用時(shí)間限制配合使用,來驗(yàn)證軟件的使用期限。
X14-X15:為注冊時(shí)間限制,2位數(shù)字字符,從生成序列號日期算起,超過此注冊時(shí)間限制,序列號將無法正常進(jìn)行注冊操作。
X16-X20:為使用時(shí)間限制,5位數(shù)字字符,與生成序列號日期配合使用來驗(yàn)證軟件使用期限。
X21:為序列號的偏移量,1位字符,不管在什么場景下,每次生成序列號的偏移量都不一樣。
X22-X25:為保留數(shù)據(jù)位,暫時(shí)不使用。自定義一個序列號字典信息,例如:_Dictionary ="JCB8EF2GH7K6MVP9QR3TXWY4",把容易混淆的字符去掉,這個可以自定義。序列號的每個部分都是通過隨機(jī)生成的偏移量(X21),對字典進(jìn)行位移,根據(jù)輸入的數(shù)字信息對應(yīng)字典的下標(biāo)提取相應(yīng)的字符作為序列號的一個字符。
生成序列號的大概過程:
反向解析大概過程類似,只需要根據(jù)X21字符,與字典的字符進(jìn)行匹配,對應(yīng)的下標(biāo)作為偏移量,就可以反向解析出各項(xiàng)信息。
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> /// 序列號字典,把數(shù)字和字母容易混淆的字符去掉。所產(chǎn)生的25位序列號從這個字典中產(chǎn)生。 /// </summary> PRivate static string _Dictionary = "JCB8EF2GH7K6MVP9QR3TXWY4"; /// <summary> /// 可以自定義字典字符串 /// </summary> public static string Dictionary { get { return _Dictionary; } set { if (value.Length < 9) { throw new ArgumentOutOfRangeException("設(shè)置的字典長度不能小于9個字符"); } _Dictionary = value; } } /// <summary> /// 生成序列號 /// </summary> /// <param name="key">關(guān)鍵字,一般為CPU號、硬盤號、網(wǎng)卡號,用于與序列號綁定,實(shí)現(xiàn)一機(jī)一碼</param> /// <param name="now">現(xiàn)在的時(shí)間</param> /// <param name="regLimitDays">注冊天數(shù)限制,超過此天數(shù),再進(jìn)行注冊,序列號就失效了,不能再使用了</param> /// <param name="useLimitDays">使用天數(shù)限制,超過此天數(shù),可以設(shè)置軟件停止運(yùn)行等操作</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("注冊天數(shù)限制范圍為0-9"); } if (useLimitDays < 0 || useLimitDays > 99999) { throw new ArgumentOutOfRangeException("使用天數(shù)限制范圍為0-99999"); } /* *關(guān)鍵字用MD5加密后,取后5個字符作為序列號第1組字符 */ string md5 = Safety.MD5(key); string x1 = md5.Substring(md5.Length - 5); /* * 生成隨機(jī)偏移量 */ Random rand = new Random(); int offset = rand.Next(1, Dictionary.Length - 1); /* * 第5組的第1個字符保存偏移量字符,其余4個字符隨機(jī)生成,作為保留位 */ string x5 = Dictionary[offset].ToString(); for (int i = 0; i < 4; i++) { x5 += Dictionary[rand.Next(0, Dictionary.Length - 1)].ToString(); } /* * 以注冊時(shí)間(yyyyMMdd)和注冊時(shí)間限制生成第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; /* *以使用時(shí)間限制生成第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">關(guān)鍵字,一般為CPU號、硬盤號、網(wǎng)卡號,用于與序列號綁定,實(shí)現(xiàn)一機(jī)一碼</param> /// <param name="sn">序列號</param> /// <param name="desc">描述信息</param> /// <returns>注冊狀態(tài),成功:0</returns> internal static int RegSn(string key, string sn, ref string desc) { if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(sn)) { throw new ArgumentNullException("參數(shù)為空"); } LicenseInfo regInfo = GetRegInfo(sn); string md5 = Safety.MD5(key); if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0) { desc = "關(guān)鍵字與序列號不匹配"; return -1;//關(guān)鍵字與序列號不匹配 } if (regInfo.Date == DateTime.MaxValue || regInfo.Date == DateTime.MinValue || regInfo.Date > DateTime.Now.Date) { desc = "序列號時(shí)間有錯誤"; return -2;//序列號時(shí)間有錯誤 } 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> /// 檢測序列號,試用于時(shí)鐘定時(shí)調(diào)用 /// </summary> /// <param name="key">關(guān)鍵字,一般為CPU號、硬盤號、網(wǎng)卡號,用于與序列號綁定,實(shí)現(xiàn)一機(jī)一碼</param> /// <param name="desc">描述信息</param> /// <returns>檢測狀態(tài),成功:0</returns> internal static int CheckSn(string key, ref string desc) { if (String.IsNullOrEmpty(key)) { throw new ArgumentNullException("參數(shù)為空"); } object val = Application.UserAppDataRegistry.GetValue("SN"); if (val == null) { desc = "未檢測到本機(jī)的序列號"; 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 = "關(guān)鍵字與序列號不匹配"; return -2;//關(guān)鍵字與序列號不匹配 } if ((DateTime.Now.Date - regInfo.Date).TotalDays > regInfo.UseLimitDays) { desc = "序列使用到期"; return -3;//關(guān)鍵字與序列號不匹配 } desc = "序列號可用"; return 0; } /// <summary> /// 獲得剩余天數(shù) /// </summary> /// <param name="key">關(guān)鍵字,一般為CPU號、硬盤號、網(wǎng)卡號,用于與序列號綁定,實(shí)現(xiàn)一機(jī)一碼</param> /// <returns>剩余天數(shù)</returns> internal static int GetRemainDays(string key) { if (String.IsNullOrEmpty(key)) { throw new ArgumentNullException("參數(shù)為空"); } 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;//關(guān)鍵字與序列號不匹配,不能使用。 } //<=0的情況,證明不可以使用。 return regInfo.UseLimitDays - (int)(DateTime.Now.Date - regInfo.Date).TotalDays; } /// <summary> /// 根據(jù)序列號,反推注冊信息 /// </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("序列號格式錯誤,應(yīng)該帶有'-'字符"); } 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> /// 以當(dāng)前時(shí)間和偏移量生成當(dāng)前時(shí)間對應(yīng)的字符串 /// </summary> /// <param name="now">當(dāng)前時(shí)間</param> /// <param name="offset">偏移量</param> /// <returns>返回日期對應(yīng)的字符串,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> /// 根據(jù)注冊時(shí)間序列號反推注冊時(shí)間 /// </summary> /// <param name="dateSn">時(shí)間字符串</param> /// <param name="offset">偏移量</param> /// <returns>時(shí)間</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> /// 以注冊時(shí)間限制和偏移量生成對應(yīng)的字符串 /// </summary> /// <param name="regLimitDays"></param> /// <param name="offset"></param> /// <returns>返回對應(yīng)的注冊時(shí)間限制的字符串,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> /// 根據(jù)注冊時(shí)間限制字符串反推注冊時(shí)間限制 /// </summary> /// <param name="regLimitSn">注冊時(shí)間限制字符串</param> /// <param name="offset">偏移量</param> /// <returns>注冊時(shí)間限制</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> /// 以使用時(shí)間限制和偏移量生成對應(yīng)的字符串 /// </summary> /// <param name="useLimitDays">使用時(shí)間限制</param> /// <param name="offset">偏移量</param> /// <returns>使用時(shí)間限制對應(yīng)字符串,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> /// 根據(jù)使用時(shí)間限制字符串反推使用時(shí)間限制 /// </summary> /// <param name="regLimitSn">使用時(shí)間限制字符串</param> /// <param name="offset">偏移量</param> /// <returns>使用時(shí)間限制</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> /// 根據(jù)字典、偏移量和偏移類型生成新的字典 /// </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(); } }
從安全角度來講,.NET程序如果不加混淆的話,很容易被反編譯出源代碼的。從專業(yè)角度來講,即使增加了序列號功能,也無濟(jì)于事,專業(yè)的人員分分鐘可以破解掉,盡管這樣干的人很少,但是存在這種可能性。如果一個軟件人員想了解一個很好的軟件,第一反映可能就是反編譯。
對于公司或商業(yè)使用的軟件來講,增加混淆還是有必要的,盡管現(xiàn)在開源很流行。
不管.NET程序如何進(jìn)行混淆,理論上都是可以破解的,理論的東西就不贅述了。通常接觸過的破解方式有兩種:注冊機(jī)方式和暴力方式。
注冊機(jī)的方式,需要通過軟件的驗(yàn)證序列號的過程和機(jī)制反向推算出序列號的生成算法,根據(jù)反推的算法開發(fā)一個小軟件,用于生成脫離作者授權(quán)生成序列號。這種方式不會破壞程序本身的代碼,相對溫和。暴力的方式,就是找到序列號驗(yàn)證部分的代碼,通過刪除或繞過驗(yàn)證代碼等方式不讓代碼有效執(zhí)行。這種方式會對程序本身的代碼進(jìn)行改動,所以也存在一些風(fēng)險(xiǎn)。
實(shí)現(xiàn)序列號有多種方式,上述方式不一定最好,但是希望對開發(fā)者有一定幫助。
最終實(shí)現(xiàn)效果圖如下:
作者:唯笑志在
Email:504547114@QQ.com
QQ:504547114
.NET開發(fā)技術(shù)聯(lián)盟:54256083
文檔下載:http://pan.baidu.com/s/1pJ7lZWf
官方網(wǎng)址:http://www.bmpj.net
新聞熱點(diǎn)
疑難解答
圖片精選