3. 深入研究 UCenter API 之 加密與解密(轉載)
阿新 • • 發佈:2017-12-25
method href img 破解 cti subst != efault times
- 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 /// 計算Md5 106 /// </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 else 119 { 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轉DataTime 182 /// </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轉PhpTime 193 /// </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 AuthCodeMethod 230 { 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 之 加密與解密(轉載)