密碼安全是認證系統的核心。本文說明現代 PHP 密碼處理標準流程:伺服端使用 password_hash() + password_verify(),客戶端可選預處理,並提供完整註冊/登入範例。
password_hash(PASSWORD_DEFAULT) (目前為 bcrypt,未來自動升級為 Argon2)password_hash 內建處理,無需手動管理password_verify():恆定時間比較,防時序攻擊CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL, -- bcrypt 60 字元,預留 255 供未來演算法
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
<?php require_once 'db.php'; // PDO 連線 $username = trim($_POST['username'] ?? ''); $email = trim($_POST['email'] ?? ''); $password = $_POST['password'] ?? ''; $errors = []; if (!$username) $errors[] = '請輸入使用者名稱'; if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Email 格式錯誤'; if (strlen($password) < 12) $errors[] = '密碼至少 12 字元'; if (!preg_match('/[A-Z]/', $password)) $errors[] = '密碼需含大寫字母'; if (!preg_match('/[a-z]/', $password)) $errors[] = '密碼需含小寫字母'; if (!preg_match('/\d/', $password)) $errors[] = '密碼需含數字'; if (!preg_match('/[^A-Za-z0-9]/', $password)) $errors[] = '密碼需含特殊符號'; // 檢查帳號是否存在 if (empty($errors)) { $stmt = $pdo->prepare('SELECT id FROM users WHERE username = ? OR email = ?'); $stmt->execute([$username, $email]); if ($stmt->fetch()) $errors[] = '帳號或 Email 已存在'; } if (empty($errors)) { // 產生密碼雜湊 (自動產生隨機鹽值、成本因子) $hash = password_hash($password, PASSWORD_DEFAULT); $stmt = $pdo->prepare('INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)'); $stmt->execute([$username, $email, $hash]); header('Location: login.php?registered=1'); exit; } // 顯示錯誤...
<?php session_start(); require_once 'db.php'; $username = trim($_POST['username'] ?? ''); $password = $_POST['password'] ?? ''; if ($username && $password) { $stmt = $pdo->prepare('SELECT id, username, password_hash FROM users WHERE username = ? OR email = ?'); $stmt->execute([$username, $username]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user && password_verify($password, $user['password_hash'])) { // 登入成功 session_regenerate_id(true); // 防 Session Fixation $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; // 可選:記錄登入 IP、時間、User-Agent header('Location: dashboard.php'); exit; } } $error = '帳號或密碼錯誤'; // 顯示登入表單...
在表單提交前,以 JavaScript 計算 SHA-256(password + '|' + username) 傳送雜湊值而非明文:
<form id="loginForm" method="post"> <input type="text" name="username" required> <input type="password" name="password" id="pwdInput" required> <input type="hidden" name="pwd_hash" id="pwdHash"> <button type="submit">登入</button> </form> <script> document.getElementById('loginForm').addEventListener('submit', async function(e) { const username = this.username.value; const password = document.getElementById('pwdInput').value; // 使用 SubtleCrypto 計算 SHA-256 (現代瀏覽器支援) const msg = new TextEncoder().encode(username + '|' + password); const hashBuffer = await crypto.subtle.digest('SHA-256', msg); const hashHex = Array.from(new Uint8Array(hashBuffer)) .map(b => b.toString(16).padStart(2, '0')).join(''); document.getElementById('pwdHash').value = hashHex; this.password.value = ''; // 清空明文欄位 }); </script>
伺服端驗證時:
$clientHash = $_POST['pwd_hash'] ?? ''; // 重新計算預期雜湊並比對 (或資料庫存雜湊的雜湊,雙層雜湊) $expectedHash = hash('sha256', $username . '|' . $password); // $password 此時為空,需調整流程 // 實務上較簡單:直接用 password_verify 配合 HTTPS 即可,客戶端雜湊增加複雜度效益有限
建議:現代瀏覽器已支援 Credential Management API 與 WebAuthn,未來應朝向無密碼認證 (Passkeys) 演進。
<?php // 登入成功後檢查是否需重新雜湊 (演算法升級、成本因子提高) if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT, ['cost' => 12])) { $newHash = password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]); $stmt = $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?'); $stmt->execute([$newHash, $user['id']]); }
| 錯誤做法 | 風險 | 正確做法 |
|---|---|---|
| MD5/SHA1 雜湊 | 極易被彩虹表/GPU 破解 | password_hash(PASSWORD_DEFAULT) |
| 固定鹽值或無鹽值 | 相同密碼產生相同雜湊,批量破解 | password_hash 內建隨機鹽值 |
| 自行實作雜湊演算法 | 極高風險,幾乎必有漏洞 | 使用經過學術驗證的標準庫 |
| 資料庫欄位長度不足 | 截斷雜湊導致無法驗證 | VARCHAR(255) 以上 |
| HTTP 傳輸密碼 | 中間人竊聽 | 全站強制 HTTPS (HSTS) |
| 密碼寫入日誌/錯誤訊息 | 內部洩露 | 過濾敏感參數、禁止記錄請求主體 |
遵循上述最佳實務,可建立符合現代安全標準的 PHP 認證系統,並具備未來演算法升級彈性。