標籤:類型 輸入 計算 box pen tool net 破解 特殊字元
- 1. 深入研究 UCenter API 之 開篇 (轉載)
- 2. 深入研究 UCenter API 之 通訊原理(轉載)
- 3. 深入研究 UCenter API 之 加密與解密(轉載)
- 4. 深入研究 UCenter API 之 網站搭建(轉載)
- 5. 深入研究 UCenter API 之 MVC 網站下的用法(轉載)
- 6. :UCenter API For .Net 在 CodePlex 上發布啦!(轉載)
AuthCode
UCenter API 中的加密解密函數,被稱為 php 領域的經典之作,也是康盛公司為 php 做的一大貢獻
這個函數,可以通過一個 KEY ,產生動態密文,並可以再通過這個 KEY 來解密
我沒有研究過什麼密碼編譯演算法,所以對這個的基礎知識也不是很瞭解,或許在 C# 中會有更強大的演算法,但是這個函數在做 UCenter API 的時候是必需的。
也是 UCenter API php 版翻譯成 C# 版本中最難的一個部分。
PHP 版詳解
1 // $string: 明文 或 密文 2 // $operation:DECODE表示解密,其它表示加密 3 // $key: 密匙 4 // $expiry:密文有效期間 5 //字串解密加密 6 function authcode($string, $operation = ‘DECODE‘, $key = ‘‘, $expiry = 0) { 7 // 動態密匙長度,相同的明文會產生不同密文就是依靠動態密匙 8 $ckey_length = 4; // 隨機密鑰長度 取值 0-32; 9 // 加入隨機密鑰,可以令密文無任何規律,即便是原文和密鑰完全相同,加密結果也會每次不同,增大破解難度。10 // 取值越大,密文變動規律越大,密文變化 = 16 的 $ckey_length 次方11 // 當此值為 0 時,則不產生隨機密鑰12 // 密匙13 $key = md5($key ? $key : UC_KEY);14 // 密匙a會參與加解密15 $keya = md5(substr($key, 0, 16));16 // 密匙b會用來做資料完整性驗證17 $keyb = md5(substr($key, 16, 16));18 // 密匙c用於變化產生的密文19 $keyc = $ckey_length ? ($operation == ‘DECODE‘ ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : ‘‘;20 // 參與運算的密匙21 $cryptkey = $keya.md5($keya.$keyc);22 $key_length = strlen($cryptkey); 23 24 // 明文,前10位用來儲存時間戳記,解密時驗證資料有效性,10到26位用來儲存$keyb(密匙b),解密時會通過這個密匙驗證資料完整性25 // 如果是解碼的話,會從第$ckey_length位開始,因為密文前$ckey_length位儲存 動態密匙,以保證解密正確26 $string = $operation == ‘DECODE‘ ? base64_decode(substr($string, $ckey_length)) : sprintf(‘%010d‘, $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;27 $string_length = strlen($string); 28 29 $result = ‘‘;30 $box = range(0, 255); 31 32 $rndkey = array();33 // 產生密匙簿34 for($i = 0; $i <= 255; $i++) {35 $rndkey[$i] = ord($cryptkey[$i % $key_length]);36 }37 // 用固定的演算法,打亂密匙簿,增加隨機性,好像很複雜,實際上對並不會增加密文的強度38 for($j = $i = 0; $i < 256; $i++) {39 $j = ($j + $box[$i] + $rndkey[$i]) % 256;40 $tmp = $box[$i];41 $box[$i] = $box[$j];42 $box[$j] = $tmp;43 }44 // 核心加解密部分45 for($a = $j = $i = 0; $i < $string_length; $i++) {46 $a = ($a + 1) % 256;47 $j = ($j + $box[$a]) % 256;48 $tmp = $box[$a];49 $box[$a] = $box[$j];50 $box[$j] = $tmp;51 // 從密匙簿得出密匙進行異或,再轉成字元52 $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));53 } 54 55 if($operation == ‘DECODE‘) {56 // 驗證資料有效性,請看未加密明文的格式57 if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {58 return substr($result, 26);59 } else {60 return ‘‘;61 }62 } else {63 // 把動態密匙儲存在密文裡,這也是為什麼同樣的明文,生產不同密文後能解密的原因64 // 因為加密後的密文可能是一些特殊字元,複製過程可能會丟失,所以用base64編碼65 return $keyc.str_replace(‘=‘, ‘‘, base64_encode($result));66 }67 }
這份詳解不是我寫的,網上有很多,找不到原作者了
C# 版
1 /// <summary> 2 /// AuthCode解碼&編碼 3 /// </summary> 4 /// <param name="sourceStr">原始字串</param> 5 /// <param name="operation">操作類型</param> 6 /// <param name="keyStr">API KEY</param> 7 /// <param name="expiry">到期時間 0代表永不到期</param> 8 /// <returns></returns> 9 private static string AuthCode(string sourceStr, AuthCodeMethod operation, string keyStr, int expiry = 0) 10 { 11 var ckeyLength = 4; 12 var source = Encode.GetBytes(sourceStr); 13 var key = Encode.GetBytes(keyStr); 14 15 key = Md5(key); 16 17 var keya = Md5(SubBytes(key, 0, 0x10)); 18 var keyb = Md5(SubBytes(key, 0x10, 0x10)); 19 var keyc = (ckeyLength > 0) 20 ? ((operation == AuthCodeMethod.Decode) 21 ? SubBytes(source, 0, ckeyLength) 22 : RandomBytes(ckeyLength)) 23 : new byte[0]; 24 25 var cryptkey = AddBytes(keya, Md5(AddBytes(keya, keyc))); 26 var keyLength = cryptkey.Length; 27 28 if (operation == AuthCodeMethod.Decode) 29 { 30 while (source.Length % 4 != 0) 31 { 32 source = AddBytes(source, Encode.GetBytes("=")); 33 } 34 source = Convert.FromBase64String(BytesToString(SubBytes(source, ckeyLength))); 35 } 36 else 37 { 38 source = 39 AddBytes( 40 (expiry != 0 41 ? Encode.GetBytes((expiry + PhpTimeNow()).ToString()) 42 : Encode.GetBytes("0000000000")), 43 SubBytes(Md5(AddBytes(source, keyb)), 0, 0x10), source); 44 } 45 46 var sourceLength = source.Length; 47 48 var box = new int[256]; 49 for (var k = 0; k < 256; k++) 50 { 51 box[k] = k; 52 } 53 54 var rndkey = new int[256]; 55 for (var i = 0; i < 256; i++) 56 { 57 rndkey[i] = cryptkey[i % keyLength]; 58 } 59 60 for (int j = 0, i = 0; i < 256; i++) 61 { 62 j = (j + box[i] + rndkey[i]) % 256; 63 var tmp = box[i]; 64 box[i] = box[j]; 65 box[j] = tmp; 66 } 67 68 var result = new byte[sourceLength]; 69 for (int a = 0, j = 0, i = 0; i < sourceLength; i++) 70 { 71 a = (a + 1) % 256; 72 j = (j + box[a]) % 256; 73 var tmp = box[a]; 74 box[a] = box[j]; 75 box[j] = tmp; 76 77 result[i] = (byte)(source[i] ^ (box[(box[a] + box[j]) % 256])); 78 } 79 80 if (operation == AuthCodeMethod.Decode) 81 { 82 var time = long.Parse(BytesToString(SubBytes(result, 0, 10))); 83 if ((time == 0 || 84 time - PhpTimeNow() > 0) && 85 BytesToString(SubBytes(result, 10, 16)) == BytesToString(SubBytes(Md5(AddBytes(SubBytes(result, 26), keyb)), 0, 16))) 86 { 87 return BytesToString(SubBytes(result, 26)); 88 } 89 return ""; 90 } 91 return BytesToString(keyc) + Convert.ToBase64String(result).Replace("=", ""); 92 } 93 94 /// <summary> 95 /// Byte數組轉字串 96 /// </summary> 97 /// <param name="b">數組</param> 98 /// <returns></returns> 99 public static string BytesToString(byte[] b)100 {101 return new string(Encode.GetChars(b));102 }103 104 /// <summary>105 /// 計算Md5106 /// </summary>107 /// <param name="b">byte數組</param>108 /// <returns>計算好的字串</returns>109 public static byte[] Md5(byte[] b)110 {111 var cryptHandler = new MD5CryptoServiceProvider();112 var hash = cryptHandler.ComputeHash(b);113 var ret = "";114 foreach (var a in hash)115 {116 if (a < 16)117 { ret += "0" + a.ToString("x"); }118 else119 { ret += a.ToString("x"); }120 }121 return Encode.GetBytes(ret);122 }123 124 /// <summary>125 /// Byte數組相加126 /// </summary>127 /// <param name="bytes">數組</param>128 /// <returns></returns>129 public static byte[] AddBytes(params byte[][] bytes)130 {131 var index = 0;132 var length = 0;133 foreach(var b in bytes)134 {135 length += b.Length;136 }137 var result = new byte[length];138 139 foreach(var bs in bytes)140 {141 foreach (var b in bs)142 {143 result[index++] = b;144 }145 }146 return result;147 }148 149 /// <summary>150 /// Byte數組分割151 /// </summary>152 /// <param name="b">數組</param>153 /// <param name="start">開始</param>154 /// <param name="length">結束</param>155 /// <returns></returns>156 public static byte[] SubBytes(byte[] b, int start, int length = int.MaxValue)157 {158 if (start >= b.Length) return new byte[0];159 if (start < 0) start = 0;160 if (length < 0) length = 0;161 if (length>b.Length || start + length > b.Length) length = b.Length - start;162 var result = new byte[length];163 var index = 0;164 for(var k = start;k< start + length;k++)165 {166 result[index++] = b[k];167 }168 return result;169 }170 171 /// <summary>172 /// 計算Php格式的目前時間173 /// </summary>174 /// <returns>Php格式的時間</returns>175 public static long PhpTimeNow()176 {177 return DateTimeToPhpTime(DateTime.UtcNow);178 }179 180 /// <summary>181 /// PhpTime轉DataTime182 /// </summary>183 /// <returns></returns>184 public static DateTime PhpTimeToDateTime(long time)185 {186 var timeStamp = new DateTime(1970, 1, 1); //得到1970年的時間戳記187 var t = (time + 8 * 60 * 60) * 10000000 + timeStamp.Ticks;188 return new DateTime(t);189 }190 191 /// <summary>192 /// DataTime轉PhpTime193 /// </summary>194 /// <param name="datetime">時間</param>195 /// <returns></returns>196 public static long DateTimeToPhpTime(DateTime datetime)197 {198 var timeStamp = new DateTime(1970, 1, 1); //得到1970年的時間戳記199 return (datetime.Ticks - timeStamp.Ticks) / 10000000; //注意這裡有時區問題,用now就要減掉8個小時200 }201 202 /// <summary>203 /// 隨機字串204 /// </summary>205 /// <param name="lens">長度</param>206 /// <returns></returns>207 public static byte[] RandomBytes(int lens)208 {209 var chArray = new[]210 {211 ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘, ‘g‘, ‘h‘, ‘j‘, ‘k‘, ‘l‘, ‘m‘, ‘n‘, ‘o‘, ‘p‘, ‘q‘,212 ‘r‘, ‘s‘, ‘t‘, ‘u‘, ‘v‘, ‘w‘, ‘x‘, ‘y‘, ‘z‘, ‘A‘, ‘B‘, ‘C‘, ‘D‘, ‘E‘, ‘F‘, ‘G‘,213 ‘H‘, ‘J‘, ‘K‘, ‘L‘, ‘M‘, ‘N‘, ‘O‘, ‘P‘, ‘Q‘, ‘R‘, ‘S‘, ‘T‘, ‘U‘, ‘V‘, ‘W‘, ‘X‘,214 ‘Y‘, ‘Z‘, ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘215 };216 var length = chArray.Length;217 var result = new byte[lens];218 var random = new Random();219 for (var i = 0; i < lens; i++)220 {221 result[i] = (byte) chArray[random.Next(length)];222 }223 return result;224 }225 226 /// <summary>227 /// 操作類型228 /// </summary>229 enum AuthCodeMethod230 {231 Encode,232 Decode,233 }
C# 版是一行一行按照原版本翻譯的,增加了一些 C# 中沒有的函數
1、string -> byte[] 的問題
在這段演算法中,經常會用到 Base64 演算法,C# 中的 Base64 要求輸入的是 byte[] 數組
在 php 程式中,都是直接用字串的,而且也沒有問題。
那在 C# 版中自然想到了 Encoding.Default.GetBytes() 函數
但這個函數有個很奇怪的問題:
Encoding.UTF8.GetBytes(((char) 200).ToString())[0].ToString() //最後的值是多少?
運行一下後發現它不是200,因為這個函數涉及到了編碼問題
所以上述的操作,如果直接對字串操作,那會出現很多問題,因為 php 和 C# 對字串使用的預設編碼不同。
所以就改成了對 byte[] 進行操作
3. 深入研究 UCenter API 之 加密與解密(轉載)