今天和大家分享一下discuz的加密和解密算法
研究了一個星期了,
總結了這個
算法有三個特點
1,動態性,同一字符串每次加密的密文都不一樣
2,時間性,可以自己加一個限期參數,以秒為單位
3,統一性,加密和解密都用同一個函數
大家可以想一想
要想每次加密后的密文都不一樣,而密鑰是同一個,那解密只有一個辦法,
就是把解密的信息肯定放到密文上面,從而解密
就像你跟不同的人下棋,對手有很多個,如果你想贏,肯定跟不同的人下就用不同的棋
法,而至於用什么棋法,就要看你的對手是個什么樣的人,有急躁的,粗心的,等等
第二時間有限性
這個大家肯定一眼就能看出來如果我判斷一個緩存過期了沒有
我可以通過它的創建時間和現在時間對比一下,用這個差值和
租期比較有沒有過期,那肯定這個也要放在密文里面
第三的話統一性的
該函數就用到異或算法,比較簡單
例如 明文是01,密鑰是10,兩個異或完之后密文就是11,
而密文再和密鑰異或就可以得到明文,也就是01
而密文和明文異或之后就可以得到密鑰10,是不是很神奇
連我自己都不相信,但事實上就是這樣,其實計算機還有很多
這樣類似的規律
function uc_authcode($string, $operation = 'DECODE', $key = '123', $expiry = 0) {
$ckey_length = 4;
$key = md5($key ? $key : UC_KEY); //加密解密時這個是不變的
$keya = md5(substr($key, 0, 16)); //加密解密時這個是不變的
$keyb = md5(substr($key, 16, 16)); //加密解密時這個是不變的
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya . md5($keya . $keyc); //64
$key_length = strlen($cryptkey); //64
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for ($i = 0; $i <= 255; $i++) { //字母表 64位后重復 數列 范圍為48~122
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for ($j = $i = 0; $i < 256; $i++) { //這里是一個打亂算法
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for ($a = $j = $i = 0; $i < $string_length; $i++) {
$result .= chr(ord($string[$i]) ^ ($box[$i]));
/* $a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
*/
}
if ($operation == 'DECODE') {
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc . str_replace('=', '', base64_encode($result));
}
}
分析一下1到5行
就是簡單的吧密鑰用md5加密,沒有密鑰的話就用uckey
得到一串32個位加密后的密鑰
$keya 作為密碼本(就是一推打亂的數字)來用
$keyb參與加密這個跟密鑰有關,解密用來驗證的
$ckey_length = 4;
然后就是ckey_length的用處就是為了
就是限制一下加密的后的字符串長度
讀者可以自己親自試一下改變 ckey_length
看看輸出來的密文一不一樣
可以看看代碼最后一句
return $keyc . str_replace('=', '', base64_encode($result));
下面分析keyc
請分析這一行
substr(md5(microtime()), -$ckey_length)) : '';
這就是為什么同一字符串加密后的密文都不一樣
就是利用了時間的唯一性,上面是返回毫秒數再用Md5返回 0到32位的字符串給keyc
好接着分析
$cryptkey = $keya . md5($keya . $keyc); //64
$key_length = strlen($cryptkey); //64
這個 生成64位的密碼本后面會用到
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$string_length = strlen($string);
到這里啦
先不看解密的就是會執行這句
sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
// 明文,前10位用來保存時間戳,解密時驗證數據有效性,10到26位用來保存$keyb的
如果沒有租期的話前十位是0的
我們在這里可以得出
這時候的長度由
$ckey_length+10時間位+16位的$keyb+明文的長度
$result = '';
$box = range(0, 255);
$rndkey = array();
for ($i = 0; $i <= 255; $i++) { //字母表 64位后重復 數列 范圍為48~122
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for ($j = $i = 0; $i < 256; $i++) { //這里是一個打亂算法
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
上面是生成256一個隨機數組
就像洗牌算法一樣
rand(0,52)
你可以循環52次來洗牌
如果重復的話再來一次rand
但這樣的效率不高
而是用
for ($i = 0; $i < 52; $i++) {
$j = rand(0,52);
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
這樣就算重復了隨機數,但每個數在其他位置還是相等的概率,讀者可以自己想一下
而上面 用了 $j = ($j + $box[$i] + $rndkey[$i]) % 256;
這句增加加了隨機性,就是什么牌一樣,第一個變了,其他也跟着變了,可以這樣說吧
第一個數隨機了,而后面的數也隨機啦,而且隨機性會越來愈大
不用rand(0,52)是為了保持加密和解密的密碼本是一樣的
for ($a = $j = $i = 0; $i < $string_length; $i++) {
$result .= chr(ord($string[$i]) ^ ($box[$i]));//先不要看這一句
/* $a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
*/
}
然后到了這里啦
其實上面也是打亂算法,再一次打亂,好像沒什么用
但我自己想啊
如果把他注釋掉
執行第一句
執行 后 也能加密也能機密,如果讀者看懂的話告訴我一聲,
后來想想可能是因為,我們第一次生成了0到255的隨機數,而$string_length這個可能沒有這么長
可能長度只有40,50啊,60啊,如果要加密的字符串很短的話,那密碼本大於長度的那些元素就用不上
啦,那就浪費啦
我想可能是這個原因吧
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
這一句就是異或的作用啦,用ascci碼異或后再轉字符,函數可以自己查查是什么
比如 chr(ord('a')) 還是 a
return $keyc . str_replace('=', '', base64_encode($result));
最后來到這句
就完成了加密啦
為什么要用base64呢
呵呵,因為異或后的字符不一定是可以打印的字符,所以要用base64來加密一下,后面有時間我說一說
base64的原理和用Php親自寫一下
最后用keyc連接呵呵,還記得我開始說的話嗎,慢慢理解一下
下面就開始分析解密的過程
上面講過的那些就不講啦
$key = md5($key ? $key : UC_KEY); //加密解密時這個是不變的
$keya = md5(substr($key, 0, 16)); //加密解密時這個是不變的
$keyb = md5(substr($key, 16, 16)); //加密解密時這個是不變的
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) :
作用是為了保證加密和加密用的那個密碼本是一樣的
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length))
這里取出后$ckey_length個字符
10時間位+16位的$keyb+明文的長度
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
如果密碼本加密和解密的一樣 那么 這句就會變回明文啦,如果不清楚異或的看上面我介紹的
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
好了,終於來到這里啦,先驗證時間有效性,和KeyB是不是用這個密鑰生成的,如果是就返回明文給他
Ok,終於分析完
其實我還有一個問題還沒有明白就是為什么密碼本長度是256個,我猜的話,因為ascii碼兩個相加不超過255
看看這句話$box[($box[$a] + $box[$j]) % 256],真的發覺discuz這個函數寫的太精彩啦
第一次寫教程,不懂排版,請大家見諒