Google 到的
<?php
function GeraHash($qtd){
//Under the string $Caracteres you write all the characters you want to be used to randomly generate the code.
$Caracteres = 'ABCDEFGHIJKLMOPQRSTUVXWYZ0123456789';
$QuantidadeCaracteres = strlen($Caracteres);
$QuantidadeCaracteres--;
$Hash=NULL;
for($x=1;$x<=$qtd;$x++){
$Posicao = rand(0,$QuantidadeCaracteres);
$Hash .= substr($Caracteres,$Posicao,1);
}
return $Hash;
}
//Here you specify how many characters the returning string must have
echo GeraHash(30);
?>
先確定一下你的需求是否只是產生一個固定長度的不重複英數字串,因為我覺得不只是這樣。youtube網址後面的參數,我猜測是為了讓數字序列不那麼容易被猜測,所以把它轉換成這個形式。
要把數字轉成不重複的英數字串(大小寫英文字母加上阿拉伯數字,共62碼),可以用簡單的轉換,例如把十進位數字轉成62進位,這樣就剛好可以跟上述英數字串對應起來。
來寫寫看,為了讓轉換過的東西更難猜,先產生一個英數字的陣列,然後把它隨機化後序列化,後續就使用這個陣列來做轉換:
<?php
$arr = [];
for($i=48;$i<58;$i++) {//阿拉伯數字
$arr[] = chr($i);
}
for($i=97;$i<(97+26);$i++) {//小寫英文字母
$arr[] = chr($i);
}
for($i=65;$i<(65+26);$i++) {//大寫英文字母
$arr[] = chr($i);
}
shuffle($arr);//隨機排列
$out = serialize($arr);//把陣列序列化成字串
file_put_contents('test1026.ser', $out);//把序列化字串寫入檔案
序列化後的陣列,內容把它存放在檔案中(也可以存進資料庫),然後要使用時再把他讀出。然後使用這個代換的陣列來做十進位與62進位的轉換:
$arr = unserialize(file_get_contents('test1026.ser'));
$t = 1474783742;
echo $t . "\n";//要轉換的數字
$d = encode62($arr, $t);
echo $d . "\n";//轉換成62進位後的表示方式
echo decode62($arr, $d) . "\n";//轉換回十進位數字
function encode62($base, $in) {//十進位轉62進位
$in = intval($in);
$base_l = count($base);
$ret = '';
do {
$m = ($in % $base_l);
$ret = $base[$m] . $ret;
$in = floor(($in - $m) / $base_l);
} while($in > 0);
return $ret;
}
function decode62($base, $in) {//62進位轉十進位
$in = strval($in);
$base_l = count($base);
$ret = 0;
$len = strlen($in);
for($i=0; $i<$len; $i++) {
$ret += array_search($in[$i], $base) * pow($base_l, $len - $i - 1);
}
return $ret;
}
但是這樣並不能讓你的英數字串固定長度是六,要讓他長度是六的話,輸入的十進位數字最大不能超過56800235583(62^6-1),最小的數字不能小餘916132832(62^5),假設你輸入的數字序列是從1開始的話,可以在所有數字轉換前先加上916132831,轉換回來的話再減掉。這樣總共可以容納55884102752個十進位數字,大約是558億多,應該夠用。
簡單說,可以用這個方式把auto_increment的primary key轉成字串使用在url上,也可以轉回來。
另外的做法是產生Hash字串,你要固定長度的字串,可以再從字串中取。不過問題是,Hash無法避免衝突,好的Hash演算法例如常用的md5、sha1等,比較不容易發生,但是並不是不可能發生,尤其是只取幾碼的狀況下。
所以使用Hash的話,你必須把Hash也存入相對應的資料表的一個unique欄位來避免衝突,萬一衝突發生,就調整產生Hash的方式。
要讓產生Hash的方式調整,可以用對於要產生Hash的輸入值做salting來達成,這樣系統可以同時存放多組salt,如果有一組salt的結果有衝突,那就換一組試試看。例如(假設你寫了一個has_conflict函數來判斷是否有重複的hash):
$salt = array('DTKB3', 'IP6G38', 'YUQ9XX', 'NDG7OB');
$id = row['id'];
$hash = md5($salt[0].$id);
$try = 1;
while(has_conflict($hash) && $try < count($salt)) {
$hash = md5($salt[$try].$id);
$try++;
}
......
換了四組salt應該不容易有衝突了XD
另外試寫了一個會產生長度為6的Hash字串的函數:
function fh6($base, $in) {
$h = array(0, 0, 0, 0, 0, 0);
$in = strval($in);
$base_l = count($base);
$touched = 0;
for($i=0; $i<strlen($in); $i++) {
if(($i%6) > $touched) $touched = $i%6;
$h[($i%6)] += ord($in[$i]);
$next = $i + 1;
if($next == strlen($in)) $next = 0;
$tmp = (ord($in[$next]) << 11) ^ $h[($i%6)];
$h[($i%6)] = ($h[($i%6)] << 16) ^ $tmp;
$h[($i%6)] += $h[($i%6)] >> 11;
}
for($i=$touched+1; $i<6; $i++) {
$h[$i] = ($h[$i-1] >> 11) ^ $h[$i];
}
$ret = '';
foreach($h as $v) {
$ret = $base[($v%$base_l)] . $ret;
}
return $ret;
}
使用上還是參照encode62:
$arr = unserialize(file_get_contents('test1026.ser'));
echo fh6($arr, 7188) . "\n";//輸出一個長度為6的Hash字串
不過這個自幹的Hash函數衝突率比較高,我用0~1000000的數字來測試,大約有千分之一的機會衝突。建議用前面提過的salting的類似方法來解決,只是改成多組$arr來替換。
(這個函數的邏輯是參考 http://www.azillionmonkeys.com/qed/hash.html 的做法)
php的md5函數,預設輸出是hex字串,不然就是byte字串。hex字串密度有點小,固定長度是40,而byte字串無法在url上使用。
比較好的方法,是把md5輸出的byte字串改成與url相容的base64編碼,不過php沒有內建,所以參考了wiki上java版本的程式碼改寫了一個。同樣是用可以輸入自定的代換陣列的方法來做,首先產生一個符合base64需求的陣列,把+
改成-
,/
改成_
,讓他產生的字串跟url相容,然後隨機排列後存檔(md5應該不容易發生衝突,要用比較標準的base64字元的順序的話,可以拿掉array_shuffle):
<?php
$arr = [];
for($i=65;$i<(65+26);$i++) {
$arr[] = chr($i);
}
for($i=97;$i<(97+26);$i++) {
$arr[] = chr($i);
}
for($i=48;$i<58;$i++) {
$arr[] = chr($i);
}
$arr[] = '-';//replace '+' with '-' for compatible with url
$arr[] = '_';//replace '/' with '_' for compatible with url
shuffle($arr);//如果要使用標準的base64字元順序,可以拿掉這一行
$out = serialize($arr);
file_put_contents('test1027.ser', $out);
然後是實作可以用這個代換表格產出base64結果編碼字串的函數:
$arr = unserialize(file_get_contents('test1027.ser'));
for($i=0; $i<100000; $i++) {
echo md5_abase64($arr, $i) . "\n";
}
function md5_abase64($base, $in) {
$hash = md5($in, true);
return abase64_encode($base, $hash);
}
function abase64_encode($base, $in) {
$base = array_unique($base);
if(count($base) != 64) return false;
$input = unpack("C*", $in);
$b = 0;
$result = '';
for($i=1; $i<count($input)+1; $i+=3) {
$b = ($input[$i] & 0xFC) >> 2;
$result .= $base[$b];
$b = ($input[$i] & 0x03) << 4;
if($i+1 < count($input)+1) {
$b |= ($input[$i+1] & 0xF0) >> 4;
$result .= $base[$b];
$b = ($input[$i+1] & 0x0F) << 2;
if($i+2 < count($input)+1) {
$b |= ($input[$i+2] & 0xC0) >> 6;
$result .= $base[$b];
$b = $input[$i+2] & 0x3F;
$result .= $base[$b];
} else {
$result .= $base[$b];
//$result .= '=';
//remove padding '=' for compatible with url
}
} else {
$result .= $base[$b];
//$result .= '==';
//remove padding '==' for compatible with url
}
}
return $result;
}
這樣輸出的長度固定是22,應該是比較好接受的長度,而且不容易發生衝突。用0~1000000的數字來測試,沒有發生衝突。(另外,base64編碼字串的長度必須是3的整數倍,否則要加=
來補齊,這個也為了跟url相容拿掉了)
我知道阿,這是很基本的PHP函數...base64的編碼表,是依照大寫英文字母、小寫英文字母、阿拉伯數字、+
、/
的順序排列,這個是不能改的(為了互通)。如果在不需要互通的狀況下,其實可以使用自定的編碼表...如果不希望別人猜內容的話。所以另外寫了一個。
另外,base64使用的+
、/
、=
三個符號,都不能用在URL裡面當做參數,為了讓base64編碼過的字串可以當做URL中的參數來傳遞,所以有一種作法,就是用-
取代+
,用_
取代/
,然後拿掉=
。前兩個透過簡單的代換就可以反運算,=
的話透過檢查base64編碼字串的長度就可以得知。
我是想說..直接用內建的 base64, 再用 str_replace 處理成 url 相容是不是方便些