產生隨機密碼是註冊系統、重設密碼、API Token 等常見需求。本文介紹三種主流演算法:ASCII 碼範圍隨機、字元集抽樣、密碼學安全隨機源,並分析其安全性、適用場景與 PHP 實作細節。
<?php
function createPasswordAscii(int $length = 8): string {
$password = '';
for ($i = 0; $i < $length; $i++) {
// ASCII 33-126:可列印字元 (含符號、大小寫、數字)
$password .= chr(mt_rand(33, 126));
}
return $password;
}
// 缺點分析:
// 1. mt_rand() 非密碼學安全,可被預測
// 2. ASCII 33-126 共 94 字元,但符號佔比過高 (33-47, 58-64, 91-96, 123-126 共 32 個符號)
// 3. 字元分布不均:數字 10 個、大寫 26、小寫 26、符號 32
?>
<?php
function generatePasswordCharset(int $length = 12): string {
// 定義字元集:可依需求調整 (如排除易混淆字元 0/O, 1/l/I)
$chars = 'abcdefghijkmnpqrstuvwxyz' // 小寫 (排除 l, o)
. 'ABCDEFGHJKLMNPQRSTUVWXYZ' // 大寫 (排除 I, O)
. '23456789' // 數字 (排除 0, 1)
. '!@#$%^&*()-_=+[]{}|;:,.<>?'; // 符號
$maxIndex = strlen($chars) - 1;
$password = '';
for ($i = 0; $i < $length; $i++) {
// 方法 A:陣列索引 (PHP 字串可當陣列)
$password .= $chars[mt_rand(0, $maxIndex)];
// 方法 B:substr (較慢)
// $password .= substr($chars, mt_rand(0, $maxIndex), 1);
}
return $password;
}
// 優點:字元集完全可控、排除易混淆字元、分布均勻
// 缺點:mt_rand() 仍非密碼學安全
?>
<?php
function generateSecurePassword(int $length = 16): string {
$chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*()-_=+';
$charCount = strlen($chars);
$bytes = random_bytes($length); // 密碼學安全隨機位元組
$password = '';
for ($i = 0; $i < $length; $i++) {
// 將位元組值映射到字元集索引 (拒絕採樣避免模數偏差)
$randVal = $bytes[$i];
$maxValid = 256 - (256 % $charCount); // 最大可接受值
while ($randVal >= $maxValid) {
// 極少見:重新取樣 (理論上平均 1 次)
$randVal = random_bytes(1)[0];
}
$password .= $chars[$randVal % $charCount];
}
return $password;
}
// 簡易版 (接受極微小模數偏差,實務可接受)
function generateSecurePasswordSimple(int $length = 16): string {
$chars = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*()-_=+';
$bytes = random_bytes($length);
return strtr(
base64_encode($bytes), // 產生 A-Z a-z 0-9 + /
'+/', // 替換不在字元集的字元
'-_' // URL-safe base64 字元
);
// 注意:此法字元集固定為 base64url,非自訂字元集
}
?>
| 方法 | 隨機源 | 預測難度 | 字元集控制 | 適用場景 | PHP 版本 |
|---|---|---|---|---|---|
| ASCII mt_rand | Mersenne Twister | 低 (觀察 624 次可破解) | 固定範圍 | 非敏感測試資料 | >= 4.2 |
| 字元集 mt_rand | Mersenne Twister | 低 | 完全可控 | 一般應用密碼、Token | >= 4.2 |
| random_int/bytes | OS CSPRNG (/dev/urandom, CryptGenRandom) | 極高 (密碼學安全) | 完全可控 | 高安全需求:密碼、API Key、CSRF Token | >= 7.0 |
| openssl_random_pseudo_bytes | OpenSSL CSPRNG | 高 | 需自行映射 | PHP 5.3-7.0 過渡方案 | >= 5.3 |
<?php
class PasswordGenerator {
private const DEFAULT_LENGTH = 16;
private const CHARSET = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*()-_=+';
public static function generate(int $length = self::DEFAULT_LENGTH): string {
if ($length < 8) throw new InvalidArgumentException('密碼長度至少 8 字元');
$bytes = random_bytes($length);
$charCount = strlen(self::CHARSET);
$maxValid = 256 - (256 % $charCount);
$password = '';
for ($i = 0; $i < $length; $i++) {
$val = $bytes[$i];
// 拒絕採樣消除模數偏差
while ($val >= $maxValid) {
$val = random_bytes(1)[0];
}
$password .= self::CHARSET[$val % $charCount];
}
return $password;
}
// 驗證密碼強度
public static function validateStrength(string $password): array {
$checks = [
'length' => strlen($password) >= 12,
'lower' => preg_match('/[a-z]/', $password),
'upper' => preg_match('/[A-Z]/', $password),
'digit' => preg_match('/\d/', $password),
'symbol' => preg_match('/[!@#$%^&*()_\-+=\[\]{}|;:,.<>?]/', $password),
'no_repeat' => !preg_match('/(.)\1{2,}/', $password), // 無連續 3 相同
];
$score = array_sum($checks);
return [
'score' => $score,
'max' => count($checks),
'checks' => $checks,
'level' => match(true) {
$score >= 6 => '極強',
$score >= 4 => '強',
$score >= 2 => '中等',
default => '弱'
}
];
}
}
// 使用
$pwd = PasswordGenerator::generate(16);
echo "密碼: $pwd\n";
print_r(PasswordGenerator::validateStrength($pwd));
?>
產生隨機密碼的核心原則:**使用密碼學安全隨機源 (random_bytes/random_int)、自訂可控字元集、排除易混淆字元、足夠長度 (>=12)、驗證強度**。PHP 7.0+ 專案**強制使用 random_bytes()**,舊專案請升級或用 openssl_random_pseudo_bytes() 過渡。切勿在安全敏感場合使用 mt_rand()、rand()、uniqid()、time() 等可預測來源。