在 URL 中常會有帶出參數的情況,其中可能有數字。例如 http://n.sfs.tw/?n=123456
但是手賤的使用者(就是我) 會企圖去修改這個數字,+1看看會出現什麼
結果就和總統府網站中馬英九的治國日記被看到明天的內容一樣出狀況,為了解決使用者改數字的問題,
設計了一個正整數加解密函數。
例如上例的123456,改為加密的字串:
使用者無法將 99ht +1,就算是變成 99hu 也無法得到文章的正確數字。
上面是想法,想直接看解法的話捲到最下面。
一、現況分析
現有的雙向 hash 函數,如 base64 ,會產生過長的字串,整體看來不美觀。因為要加密的只有數字,應該不必這麼麻煩。
參考已有的整數 HASH,這裡參考了 Thomas Wang 的網站(連結佚失),其中有他的整數HASH算法
/**
* Thomas Wang的算法,整数hash
*/
function intHash($k)
{
$k = ~$k + ($k << 15); // $k = ($k << 15) - $k - 1;
$k = $k ^ SHR($k,12);
$k = $k + ($k << 2);
$k = $k ^ SHR($k,4);
$k = $k * 2057; // $k = ($k + ($k << 3)) + ($k << 11);
$k = $k ^ SHR($k,16);
return $k;
}
function SHR($x, $n) // x >>> n
{
$mask = 0x40000000;
if ($x < 0)
{
$x &= 0x7FFFFFFF;
$mask = $mask >> ($n-1);
return ($x >> $n) | $mask;
}
return (int)$x >> (int)$n;
}
因為php並沒有 >>> 這個運算子,所以上面的 SHR 函式我把他寫成函數。
OK,看起來一切都不錯?但是?怎麼解hash值…?原站沒有提供 decode 的函式。
看來是行不通的,如果沒辦法把 HASH過的值寫回就沒意義了。
二、自寫函數初稿
所以我花了二個多小時,寫了二個整數的加解密函數:
function iencode($v){
$code="OlPkQjRiShTgUfAz5By6Cx7Dw8Ev9Fu0Gt1Hs2Ir3Jq4KpLoMnNmVeWdXcYbZa"; //想要使用的字串符,字元不得重覆。
$shift=6;
$len = strlen($code);
$v = ($v << $shift);
$out="";
do{
$r = $v%$len;
$out .= $code[$r];
$v = ($v - $r)/$len;
}while($v>0);
return $out;
}
function idecode($s){
$code="OlPkQjRiShTgUfAz5By6Cx7Dw8Ev9Fu0Gt1Hs2Ir3Jq4KpLoMnNmVeWdXcYbZa";
$shift=6;
$len = strlen($code);
$in=0;
for($ii=0;$ii<strlen($s);$ii++){
$r=strpos($code, $s[$ii]);
$in += $r* pow($len,$ii);
}
$in = ($in >>$shift);
return $in;
}
看來 WORK fine,可惜,輸入的 $v 值太大會有溢位的問題,最大只能接受 約32,000,000的正整數。
叫用例:
print iencode(123456); //99ht
print idecode("99ht"); //123456
所以上面的函式不OK。
三、自寫函數改良
再花四個小時改善原程式,加強了以下功能:
1. 可接受任意值整數,不必擔心溢位的問題;
2. 有檢查加密字串是否合法的功能
// Number Encode/Decode class By Axer 991208
class LongDEnCrypt{
var $code="OlPkQjRiShTgUfAz5By6Cx7Dw8Ev9Fu0Gt1Hs2Ir3Jq4KpLoMnNmVeWdXcYbZa"; // A character string without dupl. char.
var $shift=6; //shift value : no shift 0-10 largely shift
/**
* function LongEncode(): Encrypted a integer to a string.
* @scope public
* @param string $v input number
* @return encrypted integer string
* ex. LongEncode("5611236889745123") //IBmyw1tytILDi
*/
public function LongEncode($v){
$code= $this->code;
$shift=$this->shift;
$len = strlen($code); //62
$vs = (string)$v;
$segment = floor( log10( PHP_INT_MAX >>$shift)); //7
$segnum = ceil(strlen($vs)/ $segment);
$out="";
for($ii=0; $ii<$segnum;$ii++){
$seg = substr($vs,$ii*$segment, $segment);
$v = (int)$seg <<$shift;
if($v===0){
$zeronum =strlen($seg);
$out .= ".". $code[$zeronum];
}
else
do{
$r = $v%$len;
$out .= $code[$r];
$v = ($v - $r)/$len;
}while($v>0);
}
// Add check char.
$c=0;
for($ii=0;$ii< strlen($out);$ii++)if($out[$ii]!=='.') $c+=strpos($code, $out[$ii]);
$out .= $code[$c%$len];
return $out;
}
/**
* function LongDecode(): Decrypted a string to a number string.
* @scope public
* @param string $s encrypted string
* @return -1: string too short, -2: not a valid encrypted string and success: a number string
* ex. LongDecode("Bmyw1tytILDi"); //5611236889745123
* ex. $obj= new LongDEnCrypt();
* if( $v=$obj->LongDecode("SOMESTRING") <=0) print "Invalid encrypted string"; //Error control
* else // Do something
*/
public function LongDecode($s){
$code= $this->code;
$shift= $this->shift; //6
//check decoded string
$len = strlen($code);
$slen = strlen($s)-1;
if($slen<=1)return -1;
$c=0;
for($ii=0;$ii<$slen;$ii++)if($s[$ii]!=='.')$c+=strpos($code, $s[$ii]);
if($code[$c%$len] !== $s[$slen])return -2; //Validate Err
$segment = ceil( log( PHP_INT_MAX >>$shift)/log($len) ); //5
$out="";
$ii=0;
$index=0;
$v=0;
do{
if($s[$index] !=="."){
$r=strpos($code, $s[$index]);
$v += $r* pow($len,$ii);
if( $ii == $segment-1 || $index==$slen-1 ){
$ii=0;
$v >>= $shift;
$out .= $v;
$v=0;
}
}else{ //ZERO
$pos= strpos($code, $s[$index+1]);
$out .= str_repeat("0", $pos);
$ii=0;
$index++;
$v=0;
}
$ii++;
$index++;
}while($index<$slen);
return $out;
} //End Class
用來尚可,沒發現啥BUG
原文 2010-12-08 14:07:58