PHP; 如何安全地存储密码保护登录为cookie?

Dav*_*vid 1 php security cookies

这是我的PHP代码.

<?php
$username = "admin";
$password = "admin";
$session = $_COOKIE['session'];
$private_key = "!$//%$$//%$&=§$!&%&=§$!&%";

if(isset($_POST['login'])) {
    if($_POST['username'] == $username && $_POST['password'] == $password) {
        setcookie("username", $username, time()+(10*365*24*60*60));
        setcookie("session", md5($password.$private_key), time()+(10*365*24*60*60));
        echo "You are are logged in!";
    } else {
        echo "Wrong login!";
    }
}


if(isset($_COOKIE['session'])) {
    if($_COOKIE['username'] == $username && $_COOKIE['session'] == md5($password.$private_key)) {
        echo "You are are logged in!";
    } else {
        echo "Wrong login!";
}
}
?>


<form method="post" action="">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit" name="login">
</form>
Run Code Online (Sandbox Code Playgroud)

此代码的作用:当您使用正确的数据登录时,将使用用户名和哈希密码设置cookie.用户名不是秘密,可以以明文形式存储.在散列之前,密码与一个神秘的字符串相结合,以防止有人猜出密码.如果不知道,他就不会有成功$private_key.

重新访问该页面时,您已经因为cookie而登录.

当然我的剧本并不完美,但是:这是正确的方法吗?

如果没有正确的登录数据,您将无法登录.黑客也无法找到密码,因为它与一个神秘的字符串结合在一起.

但是黑客可以通过某种方式读取cookie数据,并且只能通过操纵浏览器中的cookie数据来使用cookie数据登录.我该怎样预防呢?

Sco*_*ski 6

如果您正在尝试实现"在此计算机上记住我"功能(在会话之上进行持久身份验证),那么您将要进入一个复杂的名副其实的兔子洞.我建议阅读这个专题指南.

总结策略:不要将密码存储在cookie中(散列或其他方式).相反,您将使用随机生成的令牌.特别是,拆分令牌.

长期身份验证("记住我在整个会话中")Cookie

<?php
class Authentication
{
    /** @var PDO $db */
    private $db;

    public function __construct(PDO $pdo)
    {
        $this->db = $pdo;
    }

    public function createLongTermToken(int $userId = 0): string
    {
        // Build the components
        $tokenLeft = base64_encode(random_bytes(15));
        $tokenRight = base64_encode(random_bytes(33));
        $tokenRightHashed = hash('sha256', $tokenRight);

        // Insert into the database
        $stmt = $this->db->prepare(
            "INSERT INTO auth_tokens (user_id, selector, hash) VALUES (?, ?, ?)"
        );
        $stmt->execute([$userId, $tokenLeft, $tokenRightHashed]);
        return $tokenLeft . ':' . $tokenRight;
    }

    public function loginWithPersistentCookie(string $cookieValue): int
    {
        // Input validation
        if (strpos(':', $cookieValue) === false) {
            throw new Exception('Invalid authentication token');
        }
        list($tokenLeft, $tokenRight) = explode(':', $cookieValue);
        if (strlen($tokenLeft) !== 20) || strlen($tokenRight) !== 44) {
            throw new Exception('Invalid authentication token');
        }
        $tokenRightHashed = hash('sha256', $tokenRight);

        // Fetch from database
        $stmt = $this->db->prepare("SELECT * FROM auth_tokens WHERE selector = ?");
        $stmt->execute([$tokenLeft]);
        // Now our token data is stored in $row:
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        // Delete token after being retrieved
        $stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE selector = ?");
        $stmt->execute([$tokenLeft]);

        // Verify the right hand side, hashed, matches the stored value:
        if (!hash_equals($row['hash'], $tokenRightHashed)) {
            throw new Exception('Invalid authentication token');
        }
        return $row['user_id'];
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

// Post-authentication, before headers are sent:
$cookieValue = $auth->createLongTermToken($userId);
setcookie(
    'long_term_auth',
    $cookieValue,
    time() + (86400 * 30), // 30 days
    '',
    '',
    TRUE, // Only send cookie over HTTPS, never unencrypted HTTP
    TRUE  // Don't expose the cookie to JavaScript
);
Run Code Online (Sandbox Code Playgroud)

在页面加载:

if (!isset($_SESSION['user_id']) && isset($_COOKIE['long_term_auth'])) {
    try {
        $_SESSION['user_id'] = $auth->loginWithPersistentCookie($_COOKIE['long_term_auth']);
    } catch (Exception $ex) {
        // Security error! Handle appropriately (i.e. log the incident).
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码片段假设它$auth是示例Authentication类的一个实例.它还假定一个基本的表结构auth_tokens(user_id指向users表,selector并且hash是VARCHAR或TEXT字段,具有唯一约束selector).

为什么这段代码安全?

  • 它将长期身份验证Cookie与用户密码分开.
  • 令牌只允许使用一次.
  • 它用于random_bytes()生成安全令牌.
  • 它使用SHA256(而不是MD5)来存储较大的auth.令牌.
  • 它用于hash_equals()比较哈希.
  • 它仅通过HTTPS发送长期身份验证Cookie.
  • 它使用PHP的内置会话管理功能.

为什么问题的代码不安全?

  • 它使用MD5,它不是安全的哈希函数.
  • 它使用==而不是hash_equals()比较哈希.另见:魔法哈希.

此外,您将要使用password_hash()password_verify()实际的用户身份验证步骤.这只是描述了"记住我"复选框便利功能的安全实现.

为了更好的安全性,您可能希望使用类似password_exposed和/或zxcvbn之类的东西来防止使用弱/泄密密码.

您的用户希望使用密码管理器(如KeePassXC,1Password或LastPass)为您的网站生成/存储他们的密码,这样他们就不会使用像您这样的弱密码admin.