password_hash密码哈希与安全

  PHP版本 >= 5.5 时,可以使用password_hash()password_verify()来对用户的密码进行加密和验证。

  对于密码加盐处理,password_hash函数现在使用的是目前PHP所支持的最强大的加密算法BCrypt。此函数未来会支持更多的加密算法。 事实上password_hash()已经处理好了加盐。加进去的随机子串通过加密算法自动保存着,成为哈希的一部分。password_verify()会把随机子串从中提取,所以不必使用另一个数据库来记录这些随机子串,大大简化了计算密码哈希值和验证密码的操作。

  注册部分:

<?php  
try {  
    // 验证email 
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); 
    if (!$email) { 
        throw new Exception('非法的Email'); 
    } 

    // 验证密码 
    $password = filter_input(INPUT_POST, 'password'); 
    if (!$password || mb_strlen($password) < 8) { 
        throw new Exception('密码长度必须大于8位'); 
    } 

    // 检测用户名是否已存在 
    $sql = "SELECT username FROM user WHERE username=:username"; 
    $stmt = $db->prepare($sql); 
    $stmt->execute(array( 
        ':username' => $email 
    )); 
    $row = $stmt->fetch(PDO::FETCH_ASSOC); 
    if ($row) { 
        exit('用户Email已存在'); 
    } 

    // 创建密码哈希值 
    $passwordHash = password_hash( 
       $password, 
       PASSWORD_DEFAULT, 
       ['cost' => 12] 
    ); 
    if ($passwordHash === false) { 
        throw new Exception('Password hash failed'); 
    } 

    $sql_insert = "INSERT INTO `user` (username,password) VALUES (:username,:password)"; 
    $stmt = $db->prepare($sql_insert); 
    $stmt->execute(array( 
        ':username' => $email, 
        ':password' => $passwordHash, 
    )); 
    $insert_id = $db->lastinsertid(); 
    if ($insert_id) { 
        // 重定向到登录页面 
        header('HTTP/1.1 302 Redirect'); 
        header('Location: login.html'); 
    } 
} catch (Exception $e) { 
    // 报告错误 
    header('HTTP/1.1 400 Bad request'); 
    echo $e->getMessage(); 
}

  登录验证:

  上述代码中,首先验证用户的Email和密码,如果不符合就抛出异常。然后使用password_hash()函数来创建密码哈希值,这个函数第一个参数是纯文本密码;第二个参数是PASSWORD_DEFAULT常量,它告诉PHP使用bcrypt哈希算法;第三个参数是一个数组,指定哈希选项,这个数组中的cost用于设定工作因子,默认是10,可以根据硬件能力提高这个值。最后创建帐户将数据写入数据表中。

<?php  
session_start();  
try {  
    // 获取post的email 
    $email = filter_input(INPUT_POST, 'email'); 

    // 获取登录密码 
    $password = filter_input(INPUT_POST, 'password'); 

    // 查找用户 
    $sql = "SELECT id,password FROM user WHERE username=:username"; 
    $stmt = $db->prepare($sql); 
    $stmt->execute(array( 
        ':username' => $email 
    )); 
    $row = $stmt->fetch(PDO::FETCH_ASSOC); 
    if (!$row) { 
        exit('用户不存在'); 
    } 

    // 验证密码 
    if (password_verify($password, $row['password']) === false) { 
        exit('密码错误!'); 
    } 

    // 保存session会话 
    $_SESSION['user_logged_in'] = 'yes'; 
    $_SESSION['user_email'] = $email; 

    // 重定向跳转 
    header('HTTP/1.1 302 Redirect'); 
    header('Location: user.php'); 
} catch (Exception $e) { 
    header('HTTP/1.1 401 Unauthorized'); 
    echo $e->getMessage(); 
}

  上述代码首先获取用户Email和密码,然后根据Email查找数据表中是否存在该Email,如果存在则将注册时加密的密码hash取出来;然后使用password_verify()函数验证密码,password_verify()函数有两个参数,第一个参数是纯文本密码,第二个参数是用户数据表中读取出来的密码hash值。如果该函数返回true,说明密码正确,否则,密码错误,终止登录。

  当过去一段时间后,想将用户加密的哈希密码增强升级,可以使用password_needs_rehash()来检查用户密码哈希值是否需要更新,比如将工作因子从10升到20,重新计算密码哈希值,让密码变得更安全。

PS:
  filter_var()filter_input()函数可以过滤文本并对格式进行验证。PHP提供了验证布尔值、Email、浮点数、整数、IP地址、MAC地址、正则表达式以及URL地址的标志。

filter_var()函数的验证标志:  
FILTER_VALIDATE_BOOLEAN: 布尔值  
FILTER_VALIDATE_EMAIL: Email  
FILTER_VALIDATE_FLOAT: 浮点数  
FILTER_VALIDATE_INT: 整数  
FILTER_VALIDATE_IP: IP地址  
FILTER_VALIDATE_MAC: MAC地址  
FILTER_VALIDATE_REGEXP: 正则表达式  
FILTER_VALIDATE_URL: URL地址