剖析 WordPress `wp_verify_password()` 函数源码:兼容 `phpass` 库的密码验证。

各位观众老爷们,晚上好!今儿咱们不聊风花雪月,也不谈人生理想,就来扒一扒WordPress里一个看似不起眼,但却至关重要的函数:wp_verify_password()。这玩意儿就像守卫着咱们账号安全的门神,专门负责验证用户输入的密码是否正确。更关键的是,它还兼容了古老的 phpass 库,保证了老用户的密码也能顺利登录。怎么样,是不是瞬间感觉它高大上了起来?

今天咱们就来做一次“源码一日游”,深入剖析一下 wp_verify_password() 的工作原理,看看它是如何兼容 phpass 的,以及其中蕴含的一些安全小技巧。准备好了吗?Let’s go!

1. wp_verify_password() 的庐山真面目

首先,咱们先来看看 wp_verify_password() 函数的基本结构。这个函数位于 wp-includes/pluggable.php 文件中,代码如下(简化版):

function wp_verify_password( $password, $hash ) {
    global $wp_hasher;

    if ( empty( $password ) || empty( $hash ) ) {
        return false;
    }

    // Check if the hash is using the old, insecure MD5.
    if ( strlen( $hash ) == 32 && preg_match( '/^[a-f0-9]{32}$/', $hash ) ) {
        return hash_equals( md5( $password ), $hash );
    }

    // If the hash is not portable, rehash it to prevent password reset issues.
    if ( strpos( $hash, '$P$' ) === 0 ) {
        require_once ABSPATH . 'wp-includes/class-phpass.php';
        $wp_hasher = new PasswordHash( 8, true ); //iterations 8, portable hashes
        return $wp_hasher->CheckPassword( $password, $hash );
    }
    // Check if the hash is using bcrypt.
    if ( strpos( $hash, '$2y$' ) === 0 ) {
        if ( ! function_exists( 'password_verify' ) ) {
            require_once ABSPATH . 'wp-includes/class-phpass.php';
            $wp_hasher = new PasswordHash( 8, true ); //iterations 8, portable hashes
            return $wp_hasher->CheckPassword( $password, $hash );
        }

        return password_verify( $password, $hash );
    }

    // Check if the hash is using argon2i.
    if ( strpos( $hash, '$argon2i$' ) === 0 ) {
        if ( ! function_exists( 'password_verify' ) ) {
            return false; // Argon2i requires PHP 7.2+
        }

        return password_verify( $password, $hash );
    }

    // Use the fallback if no other checks match.
    if ( empty( $wp_hasher ) ) {
        require_once ABSPATH . 'wp-includes/class-phpass.php';
        $wp_hasher = new PasswordHash( 8, true ); //iterations 8, portable hashes
    }

    return $wp_hasher->CheckPassword( $password, $hash );
}

简单来说,这个函数接收两个参数:

  • $password: 用户输入的明文密码。
  • $hash: 数据库中存储的密码哈希值。

它的任务就是判断用户输入的密码经过哈希后是否与数据库中的哈希值匹配。如果匹配,就返回 true,否则返回 false

2. 密码哈希的进化史:从 MD5 到 Argon2i

在深入了解 wp_verify_password() 的细节之前,咱们先来回顾一下密码哈希的进化史。毕竟,不同的哈希算法直接影响着验证过程。

  • MD5: 最古老的哈希算法之一,速度快,但安全性极低,容易被彩虹表破解。在WordPress早期版本中使用过。

  • phpass (Portable PHP password hashing framework): 一个专门为密码哈希设计的PHP库,使用了一种基于 DES 的哈希算法,并加入了盐值,提高了安全性。phpass 在 WordPress 中被广泛使用,兼容性很好。

  • bcrypt: 一种自适应的哈希算法,计算速度可调,可以抵御暴力破解。WordPress 在 PHP 5.3.7+ 环境中开始使用 bcrypt。

  • Argon2i: 一种现代的密钥派生函数,被认为是目前最安全的哈希算法之一。WordPress 在 PHP 7.2+ 环境中开始支持 Argon2i。

哈希算法 安全性 WordPress 支持版本 备注
MD5 非常低 早期版本 已经弃用,但为了兼容旧版本,仍需验证。
phpass 较高 所有版本 兼容性好,但安全性相对较低。
bcrypt PHP 5.3.7+ 需要 PHP 支持 password_hashpassword_verify 函数。
Argon2i 非常高 PHP 7.2+ 需要 PHP 支持 password_hashpassword_verify 函数,且配置为 PASSWORD_ARGON2I

3. wp_verify_password() 的工作流程:兼容性的艺术

现在,咱们回到 wp_verify_password() 函数,看看它是如何处理这些不同的哈希算法的。

  1. 空值检查: 首先,函数会检查 $password$hash 是否为空。如果为空,直接返回 false,避免出现错误。

    if ( empty( $password ) || empty( $hash ) ) {
        return false;
    }
  2. MD5 兼容: 接下来,函数会检查哈希值是否为 MD5 格式。如果是,就使用 md5() 函数对用户输入的密码进行哈希,然后使用 hash_equals() 函数进行比较。

    if ( strlen( $hash ) == 32 && preg_match( '/^[a-f0-9]{32}$/', $hash ) ) {
        return hash_equals( md5( $password ), $hash );
    }

    这里使用 hash_equals() 函数而不是 == 进行比较,是为了防止时序攻击,提高安全性。

  3. phpass 兼容: 如果哈希值以 '$P$' 开头,说明使用了 phpass 库。此时,函数会加载 class-phpass.php 文件,创建一个 PasswordHash 对象,并调用 CheckPassword() 方法进行验证。

    if ( strpos( $hash, '$P$' ) === 0 ) {
        require_once ABSPATH . 'wp-includes/class-phpass.php';
        $wp_hasher = new PasswordHash( 8, true ); //iterations 8, portable hashes
        return $wp_hasher->CheckPassword( $password, $hash );
    }

    '$P$'phpass 生成的哈希值的前缀,用于标识哈希算法。PasswordHash 对象的构造函数接收两个参数:迭代次数和是否生成 portable hashes。portable hashes 是一种特殊的 phpass 哈希,可以在不同的 PHP 环境中使用。

  4. bcrypt 兼容: 如果哈希值以 '$2y$' 开头,说明使用了 bcrypt 算法。如果 PHP 环境支持 password_verify() 函数,就直接调用该函数进行验证。否则,就回退到 phpass 库进行验证。

    if ( strpos( $hash, '$2y$' ) === 0 ) {
        if ( ! function_exists( 'password_verify' ) ) {
            require_once ABSPATH . 'wp-includes/class-phpass.php';
            $wp_hasher = new PasswordHash( 8, true ); //iterations 8, portable hashes
            return $wp_hasher->CheckPassword( $password, $hash );
        }
    
        return password_verify( $password, $hash );
    }

    '$2y$' 是 bcrypt 生成的哈希值的前缀,用于标识哈希算法。password_verify() 函数是 PHP 内置的密码验证函数,专门用于验证 bcrypt 哈希。

  5. Argon2i 兼容: 如果哈希值以 '$argon2i$' 开头,说明使用了 Argon2i 算法。如果 PHP 环境支持 password_verify() 函数,就直接调用该函数进行验证。否则,直接返回 false,因为 Argon2i 需要 PHP 7.2+ 环境。

    if ( strpos( $hash, '$argon2i$' ) === 0 ) {
        if ( ! function_exists( 'password_verify' ) ) {
            return false; // Argon2i requires PHP 7.2+
        }
    
        return password_verify( $password, $hash );
    }

    '$argon2i$' 是 Argon2i 生成的哈希值的前缀,用于标识哈希算法。

  6. 默认处理: 如果以上所有条件都不满足,就使用 phpass 库作为默认处理方式。

    if ( empty( $wp_hasher ) ) {
        require_once ABSPATH . 'wp-includes/class-phpass.php';
        $wp_hasher = new PasswordHash( 8, true ); //iterations 8, portable hashes
    }
    
    return $wp_hasher->CheckPassword( $password, $hash );

    这保证了即使遇到未知的哈希格式,也能使用 phpass 进行验证,提高兼容性。

4. phpass 库的内部机制:盐值和迭代

既然 phpasswp_verify_password() 中扮演着重要的角色,咱们就来简单了解一下它的内部机制。

phpass 的核心思想是:

  • 盐值 (Salt): 在密码哈希之前,先生成一个随机的字符串,称为盐值。将盐值和密码拼接在一起,然后再进行哈希。这样可以防止彩虹表攻击。

  • 迭代 (Iteration): 对哈希结果进行多次迭代,增加破解难度。

phpassCheckPassword() 方法会从哈希值中提取盐值和迭代次数,然后使用相同的盐值和迭代次数对用户输入的密码进行哈希,最后与数据库中的哈希值进行比较。

5. 安全提示:密码哈希的最佳实践

  • 使用最新的哈希算法: 优先使用 bcrypt 或 Argon2i 等安全性更高的哈希算法。

  • 增加迭代次数: 增加哈希的迭代次数可以提高安全性,但也会增加计算时间。需要在安全性和性能之间进行权衡。

  • 使用随机盐值: 每个密码都应该使用不同的随机盐值。

  • 定期更新密码哈希算法: 随着技术的发展,旧的哈希算法可能会被破解。定期更新密码哈希算法可以提高安全性。

6. 总结:兼容与安全的平衡

wp_verify_password() 函数是 WordPress 中一个非常重要的安全组件。它不仅负责验证用户密码,还兼容了多种不同的哈希算法,保证了老用户的密码也能顺利登录。

这个函数的设计体现了 WordPress 在兼容性和安全性之间的平衡。虽然 MD5 和 phpass 等旧的哈希算法安全性较低,但为了兼容旧版本,仍然需要进行验证。同时,WordPress 也在不断引入新的哈希算法,提高安全性。

通过今天的“源码一日游”,相信大家对 wp_verify_password() 函数的工作原理有了更深入的了解。希望这些知识能帮助大家更好地理解 WordPress 的安全机制,并编写更安全的代码。

好了,今天的讲座就到这里。感谢大家的收听!咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注