1. 程式人生 > >3. 深入研究 UCenter API 之 加密與解密(轉載)

3. 深入研究 UCenter API 之 加密與解密(轉載)

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 之 加密與解密(轉載)