首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 使用者密碼儲存與驗證安全最佳實務:雜湊、鹽值、Client-Side 預處理

     編輯:PHP 相關知識

PHP 使用者密碼儲存與驗證安全最佳實務

密碼安全是認證系統的核心。本文說明現代 PHP 密碼處理標準流程:伺服端使用 password_hash() + password_verify(),客戶端可選預處理,並提供完整註冊/登入範例。

核心原則

  1. 永不儲存明文密碼:資料庫只存雜湊值
  2. 使用內建強雜湊演算法:PHP 5.5+ password_hash(PASSWORD_DEFAULT) (目前為 bcrypt,未來自動升級為 Argon2)
  3. 每個密碼獨立隨機鹽值:password_hash 內建處理,無需手動管理
  4. 驗證使用 password_verify():恆定時間比較,防時序攻擊
  5. 傳輸層加密:全站 HTTPS,防止中間人竊聽

資料表設計

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;

註冊流程 (register.php)

<?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;
}

// 顯示錯誤...

登入流程 (login.php)

<?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 認證系統,並具備未來演算法升級彈性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved