博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
3. 深入研究 UCenter API 之 加密与解密(转载)
阅读量:6574 次
发布时间:2019-06-24

本文共 9450 字,大约阅读时间需要 31 分钟。

 

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 ///   2 /// AuthCode解码&编码  3 ///   4 /// 原始字符串  5 /// 操作类型  6 /// API KEY  7 /// 过期时间 0代表永不过期  8 /// 
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 /// 95 /// Byte数组转字符串 96 /// 97 /// 数组 98 ///
99 public static string BytesToString(byte[] b)100 {101 return new string(Encode.GetChars(b));102 }103 104 /// 105 /// 计算Md5106 /// 107 /// byte数组108 ///
计算好的字符串
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 /// 125 /// Byte数组相加126 /// 127 /// 数组128 ///
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 /// 150 /// Byte数组分割151 /// 152 /// 数组153 /// 开始154 /// 结束155 ///
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 /// 172 /// 计算Php格式的当前时间173 /// 174 ///
Php格式的时间
175 public static long PhpTimeNow()176 {177 return DateTimeToPhpTime(DateTime.UtcNow);178 }179 180 /// 181 /// PhpTime转DataTime182 /// 183 ///
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 /// 192 /// DataTime转PhpTime193 /// 194 /// 时间195 ///
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 /// 203 /// 随机字符串204 /// 205 /// 长度206 ///
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 /// 227 /// 操作类型228 /// 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[] 进行操作

 

转载于:https://www.cnblogs.com/webenh/p/8110423.html

你可能感兴趣的文章
jsp打印
查看>>
从类开始
查看>>
iOS中真机连接电脑运行程序出现问题
查看>>
java安卓如何实现定义接口
查看>>
Union大小
查看>>
南邮CTF--bypass again
查看>>
函数的渐近增长
查看>>
动态参数
查看>>
FirewallD常用命令及设置
查看>>
Slight difference between C++ and C
查看>>
c++类的嵌套(1)
查看>>
Android SqlLite数据库的创建、增、删、改、查、使用事务
查看>>
phpStorm无法使用svn1.8的解决办法
查看>>
Talk is cheap,show me the code
查看>>
[Java]知乎下巴第3集:来人啊快把知乎的答案装到篮子里去
查看>>
解决中文乱码的问题
查看>>
前端异常测试
查看>>
JSON与localStorage的爱恨情仇
查看>>
input验证码框,输入非数字或非12位时,红框提示;每4位加一个空格
查看>>
IOS上iframe的滚动条失效的解决办法
查看>>