iT邦幫忙

0

php 如何產生不重複的字串?

php

請問該如何用php產生不重複的字串?
如同youtube後面的結尾會出現的字串:6Af6b_wyiwI (網址可接受的字元)
而且可以分大小寫?

asys0512 iT邦新手 4 級 ‧ 2016-09-24 11:09:44 檢舉
而且我可以設定要出現幾個字串 例如 每次都產生4~6個 或 固定6個等等
asys0512 iT邦新手 4 級 ‧ 2016-09-24 11:10:27 檢舉
例如這次可能是aga487 下次可能是wao4e5q 、r4es4 、gw4r
https://3v4l.org/KKlc3#v700

參考

2 個回答

2
海綿寶寶
iT邦超人 1 級 ‧ 2016-09-24 21:00:04
最佳解答

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);
?>
3
fillano
iT邦超人 1 級 ‧ 2016-09-25 19:30:29

先確定一下你的需求是否只是產生一個固定長度的不重複英數字串,因為我覺得不只是這樣。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

看更多先前的回應...收起先前的回應...
fillano iT邦超人 1 級 ‧ 2016-09-25 21:18:47 檢舉

另外試寫了一個會產生長度為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 的做法)

fillano iT邦超人 1 級 ‧ 2016-09-25 23:09:43 檢舉

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相容拿掉了)

weiclin iT邦高手 7 級 ‧ 2016-09-26 01:29:16 檢舉
fillano iT邦超人 1 級 ‧ 2016-09-26 08:41:12 檢舉

我知道阿,這是很基本的PHP函數...base64的編碼表,是依照大寫英文字母、小寫英文字母、阿拉伯數字、+/的順序排列,這個是不能改的(為了互通)。如果在不需要互通的狀況下,其實可以使用自定的編碼表...如果不希望別人猜內容的話。所以另外寫了一個。

另外,base64使用的+/=三個符號,都不能用在URL裡面當做參數,為了讓base64編碼過的字串可以當做URL中的參數來傳遞,所以有一種作法,就是用-取代+,用_取代/,然後拿掉=。前兩個透過簡單的代換就可以反運算,=的話透過檢查base64編碼字串的長度就可以得知。

/images/emoticon/emoticon33.gif

weiclin iT邦高手 7 級 ‧ 2016-09-27 09:53:51 檢舉

我是想說..直接用內建的 base64, 再用 str_replace 處理成 url 相容是不是方便些

weiclin iT邦高手 7 級 ‧ 2016-09-27 09:59:26 檢舉

另外可以搭配 strtr 來達成置換字母讓一般 base64 無法解開

fillano iT邦超人 1 級 ‧ 2016-09-27 10:29:40 檢舉

嗯,也是可以XD

我有點衝過頭/images/emoticon/emoticon25.gif

asys0512 iT邦新手 4 級 ‧ 2016-09-27 19:25:02 檢舉

我要發表回答

立即登入回答