需求分析
- 產生指定長度的隨機字串,包含大小寫字母 + 數字(可選符號)。
- 適用於驗證碼、API Token、臨時密碼、檔案命名等場景。
- **安全性要求視用途而定**:驗證碼可用 `mt_rand`,Token/密碼**必須用 CSPRNG**(`random_bytes`/`random_int`)。
方案一:通用函數(mt_rand,適用非安全場景)
<?php
/**
* 產生英數混合隨機字串 (非加密安全)
* @param int $length 長度
* @param string $charset 字元集,預設大小寫+數字
* @return string
*/
function randomAlphaNum(int $length = 8, string $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'): string {
$max = strlen($charset) - 1;
$result = '';
for ($i = 0; $i < $length; $i++) {
$result .= $charset[mt_rand(0, $max)];
}
return $result;
}
// 使用
echo randomAlphaNum(4); // 例: a7K9
echo randomAlphaNum(32); // 例: xY3k9Lp2...
?>
方案二:加密安全隨機字串(random_bytes,PHP 7+ 必用)
<?php
/**
* 產生加密安全的英數混合隨機字串
* 使用 random_bytes + Base62 編碼,無模數偏差
*/
function randomSecureAlphaNum(int $length = 32): string {
// Base62 字元集
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$bytes = random_bytes($length); // 取得足夠熵
$result = '';
$mask = 0x3F; // 63 (Base62 需 6 bits,但 64 > 62,需拒絕採樣)
$charCount = strlen($chars);
for ($i = 0; $i < $length; $i++) {
// 拒絕採樣避免模數偏差
do {
$byte = $bytes[$i % strlen($bytes)];
$val = $byte & $mask;
} while ($val >= $charCount);
$result .= $chars[$val];
}
return $result;
}
// 更簡潔:random_bytes + base64url 編碼 (含 - _,非純英數)
function randomToken(int $bytes = 32): string {
return rtrim(strtr(base64_encode(random_bytes($bytes)), '+/', '-_'), '=');
}
// 使用
echo randomSecureAlphaNum(24); // 例: k7X9pL2m...
echo randomToken(32); // 例: xY3k9Lp2-abc_def...
?>
方案三:RandomLib / paragonie/random_compat(專業庫)
composer require paragonie/random_lib
<?php
use ParagonIE\RandomLib\Factory;
use ParagonIE\RandomLib\Generator;
$factory = new Factory();
$generator = $factory->getGenerator(Generator::MEDIUM_STRENGTH); // 或 HIGH_STRENGTH
// 產生指定字元集字串
$token = $generator->generateString(32, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
echo $token;
?>
場景選型指南
| 用途 | 安全等級 | 推薦函數 | 長度建議 |
| 圖形驗證碼 | 低 | `mt_rand` / `random_int` | 4-6 |
| 簡短邀請碼 | 中 | `random_int` + Base32 | 8-12 |
| API Access Token | 高 | `random_bytes` + Base64url / `randomSecureAlphaNum` | 32-64 |
| 密碼重設 Token | 高 | `random_bytes(32)` + Base64url | 32 bytes (43 chars) |
| CSRF Token | 高 | `random_bytes(32)` | 32 bytes |
| 臨時檔案名 | 中 | `uniqid('', true)` 或 `random_bytes(16)` | 16-32 |
常見錯誤與修正
| 錯誤寫法 | 問題 | 正確寫法 |
md5(uniqid(rand(), true)) | 可預測、熵不足、長度固定 32 | `bin2hex(random_bytes(16))` |
str_shuffle('abc...') | 非 CSPRNG、分布不均 | `random_int` 選索引 |
rand() / mt_rand() 用於 Token | 非加密安全、可被暴力破解 | `random_bytes` / `random_int` |
| 自訂字元集含模稜兩可字元 (0/O, 1/l/I) | 人工辨識困難 | 移除模稜字元:`23456789ABCDEFGHJKMNPQRSTUVWXYZ` |
進階:Base62 / Base58 編碼工具類
<?php
class BaseEncoder {
public const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
public const BASE58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; // 無 0 O I l
public static function encode(string $bytes, string $alphabet = self::BASE62): string {
$base = strlen($alphabet);
$num = new \GMP(gmp_init(bin2hex($bytes), 16));
$result = '';
while (gmp_cmp($num, 0) > 0) {
list($num, $rem) = gmp_div_qr($num, $base);
$result = $alphabet[gmp_intval($rem)] . $result;
}
return $result ?: $alphabet[0];
}
public static function decode(string $str, string $alphabet = self::BASE62): string {
$base = strlen($alphabet);
$num = gmp_init(0);
foreach (str_split($str) as $char) {
$val = strpos($alphabet, $char);
if ($val === false) throw new InvalidArgumentException("Invalid char: $char");
$num = gmp_add(gmp_mul($num, $base), $val);
}
return gmp_export($num);
}
}
// 使用:32 bytes 隨機 -> Base62 字串
$token = BaseEncoder::encode(random_bytes(32));
echo $token; // 約 43 字元
?>
根據安全需求選擇正確隨機源,善用 CSPRNG 專業庫,避免常見陷阱,產出既安全又易用的隨機字串。