密码安全
密码安全的重要性
密码是保护用户账户安全的第一道防线。不安全的密码存储和处理可能导致:
- 数据泄露:用户密码被窃取
- 账户被盗:攻击者使用窃取的密码登录用户账户
- 连锁攻击:用户在多个网站使用相同密码,导致其他账户也被盗
- 信任危机:影响用户对网站的信任
常见的密码安全错误
1. 明文存储密码
<?php
// 错误的做法 - 明文存储密码
class BadPasswordStorage {
public function registerUser($username, $password) {
// 直接存储明文密码
$sql = "INSERT INTO users (username, password) VALUES (?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$username, $password]);
}
public function login($username, $password) {
$sql = "SELECT * FROM users WHERE username = ? AND password = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$username, $password]);
return $stmt->fetch();
}
}
?>
2. 使用弱哈希算法
<?php
// 错误的做法 - 使用MD5或SHA1
class WeakHashing {
public function hashPassword($password) {
// MD5已被破解,不应使用
return md5($password);
}
public function hashPasswordSHA1($password) {
// SHA1也不安全
return sha1($password);
}
}
?>
3. 不使用盐值
<?php
// 错误的做法 - 不使用盐值
class NoSalt {
public function hashPassword($password) {
// 相同的密码总是产生相同的哈希值
return password_hash($password, PASSWORD_DEFAULT);
}
}
?>
密码哈希最佳实践
1. 使用现代哈希算法
PHP提供了内置的密码哈希函数,推荐使用:
<?php
class SecurePassword {
// 哈希密码
public function hashPassword($password) {
// 使用PASSWORD_DEFAULT(目前是BCRYPT)
return password_hash($password, PASSWORD_DEFAULT);
}
// 验证密码
public function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
// 检查密码是否需要重新哈希
public function needsRehash($hash) {
return password_needs_rehash($hash, PASSWORD_DEFAULT);
}
// 完整的密码验证流程
public function validateAndUpdatePassword($userId, $password) {
// 从数据库获取用户
$user = $this->getUserById($userId);
if (!$user) {
return false;
}
// 验证密码
if (!password_verify($password, $user['password_hash'])) {
return false;
}
// 检查是否需要重新哈希
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
// 生成新的哈希
$newHash = password_hash($password, PASSWORD_DEFAULT);
// 更新数据库
$this->updatePasswordHash($userId, $newHash);
}
return true;
}
}
?>
2. 自定义密码哈希类
<?php
class PasswordManager {
private $algorithm = PASSWORD_ARGON2ID;
private $options;
public function __construct() {
// 配置哈希选项
$this->options = [
'memory_cost' => 1 << 17, // 128MB
'time_cost' => 4, // 4次迭代
'threads' => 3, // 3个线程
'cost' => 12 // BCYPT成本因子(如果使用BCRYPT)
];
}
// 生成密码哈希
public function hashPassword($password) {
return password_hash($password, $this->algorithm, $this->options);
}
// 验证密码
public function verifyPassword($password, $hash) {
return password_verify($password, $hash);
}
// 生成随机密码
public function generateRandomPassword($length = 12) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, strlen($chars) - 1)];
}
return $password;
}
// 验证密码强度
public function validatePasswordStrength($password) {
$errors = [];
// 长度检查
if (strlen($password) < 8) {
$errors[] = "密码长度至少8个字符";
}
// 复杂度检查
if (!preg_match('/[A-Z]/', $password)) {
$errors[] = "密码必须包含大写字母";
}
if (!preg_match('/[a-z]/', $password)) {
$errors[] = "密码必须包含小写字母";
}
if (!preg_match('/[0-9]/', $password)) {
$errors[] = "密码必须包含数字";
}
if (!preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
$errors[] = "密码必须包含特殊字符";
}
// 常见弱密码检查
$weakPasswords = ['password', '123456', 'qwerty', 'admin', 'letmein'];
if (in_array(strtolower($password), $weakPasswords)) {
$errors[] = "不能使用常见密码";
}
return $errors;
}
// 检查密码是否在泄露数据库中
public function checkPasswordBreach($password) {
// 使用haveibeenpwned API检查密码是否泄露
$sha1 = sha1($password);
$prefix = substr($sha1, 0, 5);
$suffix = substr($sha1, 5);
$url = "https://api.pwnedpasswords.com/range/$prefix";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'Password Checker',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => 10
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$lines = explode("\r\n", $response);
foreach ($lines as $line) {
list($hashSuffix, $count) = explode(':', $line);
if (strtoupper($hashSuffix) === $suffix) {
return (int)$count; // 返回泄露次数
}
}
}
return 0; // 未找到泄露记录
}
// 创建密码重置令牌
public function createPasswordResetToken($userId) {
$token = bin2hex(random_bytes(32));
$expires = time() + 3600; // 1小时后过期
// 存储到数据库
$sql = "INSERT INTO password_resets (user_id, token, expires) VALUES (?, ?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $token, $expires]);
return $token;
}
// 验证密码重置令牌
public function validatePasswordResetToken($token) {
$sql = "SELECT user_id, expires FROM password_resets WHERE token = ? AND used = 0";
$stmt = $this->db->prepare($sql);
$stmt->execute([$token]);
$result = $stmt->fetch();
if (!$result) {
return false;
}
// 检查是否过期
if (time() > $result['expires']) {
return false;
}
return $result['user_id'];
}
// 清理过期的重置令牌
public function cleanupExpiredTokens() {
$sql = "DELETE FROM password_resets WHERE expires < ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([time()]);
}
}
?>
用户注册和登录实现
完整的用户认证系统
<?php
class UserAuthentication {
private $db;
private $passwordManager;
private $maxLoginAttempts = 5;
private $lockoutTime = 900; // 15分钟
public function __construct($database) {
$this->db = $database;
$this->passwordManager = new PasswordManager();
}
// 用户注册
public function register($userData) {
try {
// 验证输入数据
$this->validateRegistrationData($userData);
// 检查用户是否已存在
if ($this->userExists($userData['email'])) {
throw new Exception('该邮箱已被注册');
}
// 验证密码强度
$strengthErrors = $this->passwordManager->validatePasswordStrength($userData['password']);
if (!empty($strengthErrors)) {
throw new Exception(implode(', ', $strengthErrors));
}
// 检查密码是否泄露
$breachCount = $this->passwordManager->checkPasswordBreach($userData['password']);
if ($breachCount > 0) {
throw new Exception("此密码已被泄露{$breachCount}次,请使用其他密码");
}
// 生成密码哈希
$passwordHash = $this->passwordManager->hashPassword($userData['password']);
// 创建用户
$userId = $this->createUser([
'username' => $userData['username'],
'email' => $userData['email'],
'password_hash' => $passwordHash,
'created_at' => date('Y-m-d H:i:s'),
'status' => 'active'
]);
// 发送欢迎邮件
$this->sendWelcomeEmail($userData['email'], $userData['username']);
return $userId;
} catch (Exception $e) {
error_log("注册失败: " . $e->getMessage());
throw $e;
}
}
// 用户登录
public function login($email, $password, $remember = false) {
try {
// 检查账户锁定状态
if ($this->isAccountLocked($email)) {
throw new Exception('账户已被锁定,请稍后再试');
}
// 获取用户
$user = $this->getUserByEmail($email);
if (!$user) {
$this->recordFailedLogin($email);
throw new Exception('邮箱或密码错误');
}
// 验证密码
if (!$this->passwordManager->verifyPassword($password, $user['password_hash'])) {
$this->recordFailedLogin($email);
throw new Exception('邮箱或密码错误');
}
// 检查账户状态
if ($user['status'] !== 'active') {
throw new Exception('账户未激活或已被禁用');
}
// 检查是否需要重新哈希密码
if (password_needs_rehash($user['password_hash'], PASSWORD_DEFAULT)) {
$newHash = $this->passwordManager->hashPassword($password);
$this->updatePasswordHash($user['id'], $newHash);
}
// 清除失败登录记录
$this->clearFailedLogins($email);
// 创建会话
$sessionId = $this->createSession($user['id'], $remember);
// 记录成功登录
$this->recordSuccessfulLogin($user['id']);
return [
'user_id' => $user['id'],
'session_id' => $sessionId,
'user' => [
'id' => $user['id'],
'username' => $user['username'],
'email' => $user['email']
]
];
} catch (Exception $e) {
error_log("登录失败 [{$email}]: " . $e->getMessage());
throw $e;
}
}
// 修改密码
public function changePassword($userId, $currentPassword, $newPassword) {
try {
// 获取用户
$user = $this->getUserById($userId);
if (!$user) {
throw new Exception('用户不存在');
}
// 验证当前密码
if (!$this->passwordManager->verifyPassword($currentPassword, $user['password_hash'])) {
throw new Exception('当前密码错误');
}
// 验证新密码强度
$strengthErrors = $this->passwordManager->validatePasswordStrength($newPassword);
if (!empty($strengthErrors)) {
throw new Exception(implode(', ', $strengthErrors));
}
// 检查新密码是否泄露
$breachCount = $this->passwordManager->checkPasswordBreach($newPassword);
if ($breachCount > 0) {
throw new Exception("此密码已被泄露{$breachCount}次,请使用其他密码");
}
// 检查新密码不能与最近3次密码相同
if ($this->isPasswordReused($userId, $newPassword)) {
throw new Exception('新密码不能与最近3次使用的密码相同');
}
// 生成新密码哈希
$newHash = $this->passwordManager->hashPassword($newPassword);
// 保存旧密码到历史记录
$this->savePasswordHistory($userId, $user['password_hash']);
// 更新密码
$this->updatePasswordHash($userId, $newHash);
// 使所有其他会话失效
$this->invalidateAllSessions($userId);
// 发送密码修改通知
$this->sendPasswordChangeNotification($user['email']);
return true;
} catch (Exception $e) {
error_log("修改密码失败 [用户ID: {$userId}]: " . $e->getMessage());
throw $e;
}
}
// 密码重置
public function requestPasswordReset($email) {
try {
$user = $this->getUserByEmail($email);
if (!$user) {
// 为了安全,即使用户不存在也返回成功
return true;
}
// 生成重置令牌
$token = $this->passwordManager->createPasswordResetToken($user['id']);
// 发送重置邮件
$this->sendPasswordResetEmail($email, $token);
return true;
} catch (Exception $e) {
error_log("请求密码重置失败 [{$email}]: " . $e->getMessage());
throw $e;
}
}
// 执行密码重置
public function resetPassword($token, $newPassword) {
try {
// 验证令牌
$userId = $this->passwordManager->validatePasswordResetToken($token);
if (!$userId) {
throw new Exception('重置令牌无效或已过期');
}
// 验证新密码强度
$strengthErrors = $this->passwordManager->validatePasswordStrength($newPassword);
if (!empty($strengthErrors)) {
throw new Exception(implode(', ', $strengthErrors));
}
// 检查密码是否泄露
$breachCount = $this->passwordManager->checkPasswordBreach($newPassword);
if ($breachCount > 0) {
throw new Exception("此密码已被泄露{$breachCount}次,请使用其他密码");
}
// 生成新密码哈希
$newHash = $this->passwordManager->hashPassword($newPassword);
// 更新密码
$this->updatePasswordHash($userId, $newHash);
// 标记令牌为已使用
$this->markTokenAsUsed($token);
// 使所有会话失效
$this->invalidateAllSessions($userId);
// 发送密码重置通知
$user = $this->getUserById($userId);
$this->sendPasswordResetNotification($user['email']);
return true;
} catch (Exception $e) {
error_log("密码重置失败: " . $e->getMessage());
throw $e;
}
}
// 辅助方法
private function validateRegistrationData($userData) {
$required = ['username', 'email', 'password', 'password_confirm'];
foreach ($required as $field) {
if (empty($userData[$field])) {
throw new Exception("字段 {$field} 是必填的");
}
}
if ($userData['password'] !== $userData['password_confirm']) {
throw new Exception('两次输入的密码不一致');
}
if (!filter_var($userData['email'], FILTER_VALIDATE_EMAIL)) {
throw new Exception('邮箱格式不正确');
}
if (strlen($userData['username']) < 3 || strlen($userData['username']) > 50) {
throw new Exception('用户名长度必须在3-50字符之间');
}
}
private function userExists($email) {
$sql = "SELECT id FROM users WHERE email = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$email]);
return $stmt->fetch() !== false;
}
private function isAccountLocked($email) {
$sql = "SELECT COUNT(*) as attempts FROM login_attempts
WHERE email = ? AND created_at > ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$email, time() - $this->lockoutTime]);
$result = $stmt->fetch();
return $result['attempts'] >= $this->maxLoginAttempts;
}
private function recordFailedLogin($email) {
$sql = "INSERT INTO login_attempts (email, ip_address, created_at) VALUES (?, ?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$email, $_SERVER['REMOTE_ADDR'], time()]);
}
private function clearFailedLogins($email) {
$sql = "DELETE FROM login_attempts WHERE email = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$email]);
}
private function createSession($userId, $remember = false) {
$sessionId = bin2hex(random_bytes(32));
$expires = $remember ? time() + 2592000 : time() + 3600; // 30天或1小时
$sql = "INSERT INTO user_sessions (session_id, user_id, expires, created_at)
VALUES (?, ?, ?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$sessionId, $userId, $expires, time()]);
// 设置Cookie
setcookie('session_id', $sessionId, $expires, '/', '', true, true);
return $sessionId;
}
// 其他辅助方法...
private function createUser($data) { /* 实现 */ }
private function getUserByEmail($email) { /* 实现 */ }
private function getUserById($id) { /* 实现 */ }
private function updatePasswordHash($userId, $hash) { /* 实现 */ }
private function sendWelcomeEmail($email, $username) { /* 实现 */ }
private function sendPasswordResetEmail($email, $token) { /* 实现 */ }
private function recordSuccessfulLogin($userId) { /* 实现 */ }
private function isPasswordReused($userId, $password) { /* 实现 */ }
private function savePasswordHistory($userId, $hash) { /* 实现 */ }
private function invalidateAllSessions($userId) { /* 实现 */ }
private function sendPasswordChangeNotification($email) { /* 实现 */ }
private function sendPasswordResetNotification($email) { /* 实现 */ }
private function markTokenAsUsed($token) { /* 实现 */ }
}
?>
密码策略实施
1. 密码策略配置类
<?php
class PasswordPolicy {
private $config;
public function __construct($config = []) {
$this->config = array_merge([
'min_length' => 8,
'max_length' => 128,
'require_uppercase' => true,
'require_lowercase' => true,
'require_numbers' => true,
'require_special' => true,
'forbidden_patterns' => [],
'forbidden_passwords' => [],
'max_age_days' => 90,
'history_count' => 5,
'max_attempts' => 3,
'lockout_minutes' => 30
], $config);
}
// 验证密码是否符合策略
public function validate($password, $userData = []) {
$errors = [];
// 长度检查
if (strlen($password) < $this->config['min_length']) {
$errors[] = "密码长度不能少于 {$this->config['min_length']} 个字符";
}
if (strlen($password) > $this->config['max_length']) {
$errors[] = "密码长度不能超过 {$this->config['max_length']} 个字符";
}
// 字符类型检查
if ($this->config['require_uppercase'] && !preg_match('/[A-Z]/', $password)) {
$errors[] = "密码必须包含大写字母";
}
if ($this->config['require_lowercase'] && !preg_match('/[a-z]/', $password)) {
$errors[] = "密码必须包含小写字母";
}
if ($this->config['require_numbers'] && !preg_match('/[0-9]/', $password)) {
$errors[] = "密码必须包含数字";
}
if ($this->config['require_special'] && !preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
$errors[] = "密码必须包含特殊字符";
}
// 禁用模式检查
foreach ($this->config['forbidden_patterns'] as $pattern) {
if (preg_match($pattern, $password)) {
$errors[] = "密码包含不允许的模式";
break;
}
}
// 常见弱密码检查
if (in_array(strtolower($password), $this->config['forbidden_passwords'])) {
$errors[] = "不能使用常见弱密码";
}
// 包含用户信息检查
if (!empty($userData)) {
if (isset($userData['username']) && stripos($password, $userData['username']) !== false) {
$errors[] = "密码不能包含用户名";
}
if (isset($userData['email']) && stripos($password, explode('@', $userData['email'])[0]) !== false) {
$errors[] = "密码不能包含邮箱前缀";
}
if (isset($userData['first_name']) && stripos($password, $userData['first_name']) !== false) {
$errors[] = "密码不能包含姓名";
}
if (isset($userData['last_name']) && stripos($password, $userData['last_name']) !== false) {
$errors[] = "密码不能包含姓氏";
}
}
// 序列字符检查
if ($this->hasSequentialChars($password)) {
$errors[] = "密码不能包含连续字符(如123、abc)";
}
// 重复字符检查
if ($this->hasRepeatingChars($password)) {
$errors[] = "密码不能包含过多重复字符";
}
return $errors;
}
// 检查连续字符
private function hasSequentialChars($password) {
$length = strlen($password);
for ($i = 0; $i < $length - 2; $i++) {
$char1 = ord(strtolower($password[$i]));
$char2 = ord(strtolower($password[$i + 1]));
$char3 = ord(strtolower($password[$i + 2]));
// 检查连续递增(如abc, 123)
if ($char2 == $char1 + 1 && $char3 == $char2 + 1) {
return true;
}
// 检查连续递减(如cba, 321)
if ($char2 == $char1 - 1 && $char3 == $char2 - 1) {
return true;
}
}
return false;
}
// 检查重复字符
private function hasRepeatingChars($password) {
// 检查是否有超过3个相同字符连续出现
if (preg_match('/(.)\1{3,}/', $password)) {
return true;
}
// 检查重复模式
$patterns = [
'/(.)\1\1\1/', // 4个相同字符
'/(..)\1/', // 2个字符重复2次
'/(...)\1/' // 3个字符重复2次
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $password)) {
return true;
}
}
return false;
}
// 生成密码强度评分
public function getStrengthScore($password) {
$score = 0;
// 长度评分
$length = strlen($password);
if ($length >= 8) $score += 10;
if ($length >= 12) $score += 10;
if ($length >= 16) $score += 10;
// 字符类型评分
if (preg_match('/[a-z]/', $password)) $score += 10;
if (preg_match('/[A-Z]/', $password)) $score += 10;
if (preg_match('/[0-9]/', $password)) $score += 10;
if (preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) $score += 10;
// 复杂度评分
if (preg_match('/[a-z].*[A-Z]|[A-Z].*[a-z]/', $password)) $score += 10;
if (preg_match('/[a-zA-Z].*[0-9]|[0-9].*[a-zA-Z]/', $password)) $score += 10;
if (preg_match('/[a-zA-Z0-9].*[!@#$%^&*(),.?":{}|<>]|[!@#$%^&*(),.?":{}|<>].*[a-zA-Z0-9]/', $password)) $score += 10;
// 扣分项
if ($this->hasSequentialChars($password)) $score -= 10;
if ($this->hasRepeatingChars($password)) $score -= 10;
return max(0, min(100, $score));
}
// 获取密码强度等级
public function getStrengthLevel($password) {
$score = $this->getStrengthScore($password);
if ($score < 30) return 'Weak';
if ($score < 60) return 'Fair';
if ($score < 80) return 'Good';
return 'Strong';
}
}
?>
2. 密码过期提醒
<?php
class PasswordExpirationManager {
private $db;
private $warningDays = 7;
private $graceLogins = 3;
public function __construct($database) {
$this->db = $database;
}
// 检查密码是否即将过期
public function checkPasswordExpiration($userId) {
$sql = "SELECT password_changed_at, grace_logins_used
FROM users WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId]);
$user = $stmt->fetch();
if (!$user) {
return null;
}
$passwordAge = time() - strtotime($user['password_changed_at']);
$maxAge = 90 * 24 * 60 * 60; // 90天
$remainingDays = ceil(($maxAge - $passwordAge) / (24 * 60 * 60));
if ($remainingDays <= 0) {
return [
'expired' => true,
'grace_logins_remaining' => $this->graceLogins - $user['grace_logins_used']
];
}
if ($remainingDays <= $this->warningDays) {
return [
'expiring_soon' => true,
'days_remaining' => $remainingDays
];
}
return [
'valid' => true,
'days_remaining' => $remainingDays
];
}
// 发送密码过期提醒
public function sendExpirationReminders() {
// 查找7天内密码过期的用户
$sql = "SELECT id, email, username
FROM users
WHERE password_changed_at < DATE_SUB(NOW(), INTERVAL 83 DAY)
AND password_changed_at > DATE_SUB(NOW(), INTERVAL 90 DAY)
AND status = 'active'";
$stmt = $this->db->query($sql);
$users = $stmt->fetchAll();
foreach ($users as $user) {
$this->sendExpirationEmail($user);
}
}
// 强制密码修改
public function requirePasswordChange($userId) {
$status = $this->checkPasswordExpiration($userId);
if ($status['expired'] ?? false) {
if ($status['grace_logins_remaining'] <= 0) {
// 锁定账户
$this->lockAccountForPassword($userId);
return ['locked' => true];
} else {
return ['force_change' => true, 'grace_logins' => $status['grace_logins_remaining']];
}
}
return [];
}
// 使用宽限登录
public function useGraceLogin($userId) {
$sql = "UPDATE users SET grace_logins_used = grace_logins_used + 1
WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId]);
}
private function sendExpirationEmail($user) {
$subject = "密码即将过期提醒";
$message = "亲爱的 {$user['username']},\n\n";
$message .= "您的密码将在7天后过期,请及时修改密码以确保账户安全。\n\n";
$message .= "如需帮助,请联系客服。\n\n";
$message .= "此邮件由系统自动发送,请勿回复。";
// 实际应用中应使用邮件发送库
// mail($user['email'], $subject, $message);
}
private function lockAccountForPassword($userId) {
$sql = "UPDATE users SET status = 'locked' WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId]);
}
}
?>
密码安全工具
1. 密码生成器
<?php
class PasswordGenerator {
private $lowercase = 'abcdefghijklmnopqrstuvwxyz';
private $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
private $numbers = '0123456789';
private $symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
private $ambiguous = 'ilLo0O1';
public function generate($length = 12, $options = []) {
$defaults = [
'include_uppercase' => true,
'include_lowercase' => true,
'include_numbers' => true,
'include_symbols' => true,
'exclude_ambiguous' => false,
'exclude_similar' => false
];
$options = array_merge($defaults, $options);
$chars = '';
if ($options['include_lowercase']) {
$chars .= $this->lowercase;
}
if ($options['include_uppercase']) {
$chars .= $this->uppercase;
}
if ($options['include_numbers']) {
$chars .= $this->numbers;
}
if ($options['include_symbols']) {
$chars .= $this->symbols;
}
if ($options['exclude_ambiguous']) {
$chars = str_replace(str_split($this->ambiguous), '', $chars);
}
if ($options['exclude_similar']) {
$chars = str_replace(['l', '1', 'I', 'o', '0', 'O'], '', $chars);
}
if (empty($chars)) {
throw new Exception('没有可用的字符生成密码');
}
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, strlen($chars) - 1)];
}
return $password;
}
// 生成符合特定强度的密码
public function generateStrong($minLength = 12) {
$length = max($minLength, 16);
// 确保包含所有类型的字符
$password = '';
$password .= $this->lowercase[random_int(0, strlen($this->lowercase) - 1)];
$password .= $this->uppercase[random_int(0, strlen($this->uppercase) - 1)];
$password .= $this->numbers[random_int(0, strlen($this->numbers) - 1)];
$password .= $this->symbols[random_int(0, strlen($this->symbols) - 1)];
// 填充剩余长度
for ($i = 4; $i < $length; $i++) {
$allChars = $this->lowercase . $this->uppercase . $this->numbers . $this->symbols;
$password .= $allChars[random_int(0, strlen($allChars) - 1)];
}
return str_shuffle($password);
}
// 生成易于记忆的密码(密码短语)
public function generatePassphrase($wordCount = 4, $separator = '-', $capitalize = true) {
$words = [
'apple', 'banana', 'coffee', 'dragon', 'elephant', 'flower', 'guitar', 'honey',
'island', 'jungle', 'kitten', 'lemon', 'mountain', 'nature', 'ocean', 'piano',
'queen', 'river', 'sunset', 'tiger', 'umbrella', 'village', 'window', 'yellow'
];
$selectedWords = [];
for ($i = 0; $i < $wordCount; $i++) {
$selectedWords[] = $words[random_int(0, count($words) - 1)];
}
$passphrase = implode($separator, $selectedWords);
if ($capitalize) {
$passphrase = ucwords($passphrase);
}
// 添加数字和符号增强安全性
$passphrase .= random_int(10, 99) . '!';
return $passphrase;
}
}
?>
2. 密码安全检查工具
<?php
class PasswordSecurityChecker {
// 检查密码是否在常见密码列表中
public function isCommonPassword($password) {
$commonPasswords = file('common_passwords.txt', FILE_IGNORE_NEW_LINES);
return in_array(strtolower($password), array_map('strtolower', $commonPasswords));
}
// 检查密码是否泄露(使用本地数据库)
public function checkLocalBreach($password) {
$hash = sha1($password);
$prefix = substr($hash, 0, 2);
// 查找本地数据库
$filename = "password_breaches/{$prefix}_hashes.txt";
if (file_exists($filename)) {
$hashes = file($filename, FILE_IGNORE_NEW_LINES);
foreach ($hashes as $line) {
list($hashSuffix, $count) = explode(':', $line);
if ($hashSuffix === substr($hash, 2)) {
return (int)$count;
}
}
}
return 0;
}
// 检查密码模式
public function checkPatterns($password) {
$patterns = [
'keyboard_sequence' => $this->isKeyboardSequence($password),
'repeating_chars' => $this->hasRepeatingPattern($password),
'date_pattern' => $this->containsDate($password),
'phone_pattern' => $this->containsPhone($password)
];
return $patterns;
}
private function isKeyboardSequence($password) {
$sequences = [
'qwertyuiop', 'asdfghjkl', 'zxcvbnm',
'qwertyuiop', 'asdfghjkl', 'zxcvbnm',
'1234567890', '0987654321',
'abcdefghijklmnopqrstuvwxyz',
'zyxwvutsrqponmlkjihgfedcba'
];
$lowerPassword = strtolower($password);
foreach ($sequences as $seq) {
if (strpos($lowerPassword, $seq) !== false) {
return true;
}
}
return false;
}
private function hasRepeatingPattern($password) {
return preg_match('/(.)\1{2,}/', $password) === 1;
}
private function containsDate($password) {
// 检查常见的日期格式
$patterns = [
'/\d{4}[-\/]\d{1,2}[-\/]\d{1,2}/', // YYYY-MM-DD
'/\d{1,2}[-\/]\d{1,2}[-\/]\d{4}/', // MM-DD-YYYY
'/\d{2}\d{2}\d{4}/', // MMDDYYYY
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $password)) {
return true;
}
}
return false;
}
private function containsPhone($password) {
// 检查电话号码模式
return preg_match('/\d{10,11}/', $password) === 1;
}
}
?>
最佳实践总结
1. 密码存储原则
<?php
// 永远不要这样做
$hash = md5($password);
// 正确的做法
$hash = password_hash($password, PASSWORD_ARGON2ID);
?>
2. 密码传输安全
<?php
// 强制使用HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
$redirectUrl = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
header('Location: ' . $redirectUrl);
exit;
}
// 设置安全Cookie
setcookie('auth_token', $token, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => true, // 仅HTTPS
'httponly' => true, // 禁止JavaScript访问
'samesite' => 'Strict' // 防止CSRF
]);
?>
3. 完整的密码管理流程
<?php
class PasswordLifecycle {
// 1. 创建强密码
public function createStrongPassword() {
$generator = new PasswordGenerator();
return $generator->generateStrong();
}
// 2. 安全存储
public function storePassword($password) {
return password_hash($password, PASSWORD_ARGON2ID);
}
// 3. 验证登录
public function authenticate($password, $hash) {
if (!password_verify($password, $hash)) {
return false;
}
// 检查是否需要更新哈希
if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
$newHash = password_hash($password, PASSWORD_ARGON2ID);
// 更新数据库中的哈希
$this->updatePasswordHash($newHash);
}
return true;
}
// 4. 定期更换
public function shouldChangePassword($lastChanged) {
$maxAge = 90 * 24 * 60 * 60; // 90天
return (time() - $lastChanged) > $maxAge;
}
}
?>
通过实施这些密码安全措施,你可以大大提高用户账户的安全性,保护用户数据免受攻击。记住,密码安全是一个持续的过程,需要定期审查和更新安全策略。