好嘞!各位观众老爷,晚上好!今天咱们来聊聊 WordPress 密码加密的那些事儿,保证让大家听得明白,看得过瘾!
开场白:密码安全,马虎不得!
话说网络世界,信息安全那是重中之重。而密码,作为保护我们账号的第一道防线,更是得小心呵护。想想看,如果你的密码被人轻易破解,那可就惨了,轻则账号被盗,重则财产受损。
WordPress 作为一个流行的内容管理系统,对密码安全自然非常重视。它的密码加密机制,那可是经过精心设计的。今天,咱们就来扒一扒 WordPress 中 wp_hash_password()
函数的底裤,看看它是如何使用 phpass
库来保护我们的密码的。
第一章:wp_hash_password()
函数概览
首先,咱们来认识一下今天的主角:wp_hash_password()
函数。这个函数位于 wp-includes/pluggable.php
文件中,它的主要作用就是对用户输入的密码进行加密,然后存储到数据库中。
/**
* Hashes a password using the portable PHP password hashing framework.
*
* @since 2.5.0
*
* @param string $password The password to hash.
* @return string The hashed password.
*/
function wp_hash_password( $password ) {
global $wp_hasher;
if ( empty( $wp_hasher ) ) {
require_once ABSPATH . 'wp-includes/class-phpass.php';
$wp_hasher = new PasswordHash( 8, true );
}
return $wp_hasher->HashPassword( trim( $password ) );
}
这段代码看起来是不是很简单?别急,咱们一步一步来分析。
-
全局变量
$wp_hasher
: 这个变量用来存储PasswordHash
类的实例。这样做的好处是,避免每次调用wp_hash_password()
函数时都重新创建PasswordHash
对象,从而提高性能。 -
empty( $wp_hasher )
: 判断$wp_hasher
是否为空。如果为空,说明是第一次调用wp_hash_password()
函数,需要先加载class-phpass.php
文件,并创建PasswordHash
类的实例。 -
require_once ABSPATH . 'wp-includes/class-phpass.php'
: 加载class-phpass.php
文件。这个文件包含了phpass
库的核心代码。ABSPATH
是 WordPress 的根目录。 -
$wp_hasher = new PasswordHash( 8, true )
: 创建PasswordHash
类的实例。PasswordHash
类是phpass
库的核心类,用于密码的加密和验证。构造函数的两个参数分别是:8
: 代表 hashing iterations 的数量。这个值越大,加密强度越高,但同时也会消耗更多的 CPU 资源。WordPress 默认值是8
。true
: 代表 portable hashes。设置成 true 会使用 MD5 加密 salt。
-
return $wp_hasher->HashPassword( trim( $password ) )
: 调用PasswordHash
类的HashPassword()
方法对密码进行加密。trim( $password )
用于去除密码两端的空格。
第二章:phpass
库的奥秘
phpass
(Portable PHP password hashing framework) 是一个用于密码哈希的 PHP 库,由 Openwall Project 开发。它的主要特点是:
- 可移植性: 可以在不同的 PHP 环境下运行,不需要依赖特定的 PHP 扩展。
- 安全性: 使用了 bcrypt 算法(如果可用)或者 MD5 算法,并加入了 salt,可以有效防止彩虹表攻击。
- 简单易用: 提供简单的 API,方便开发者使用。
现在,咱们深入到 class-phpass.php
文件中,看看 PasswordHash
类的代码。
<?php
#
# Portable PHP password hashing framework.
#
# Version 0.3 / genuine.
#
# Written by Solar Designer <solar at openwall.com> in 2005-2006 and placed in
# the public domain. Revised in subsequent years, still public domain.
#
# There's absolutely no warranty.
#
# The homepage URL for this framework is:
#
# http://www.openwall.com/phpass/
#
# Please be sure to update this comment in all of your copies of the code.
#
# Please make sure that you use this code as is. Changing it may render
# it insecure. This comment must be preserved as well.
#
class PasswordHash {
var $itoa64;
var $iteration_count_log2;
var $portable_hashes;
var $random_state;
function PasswordHash($iteration_count_log2, $portable_hashes)
{
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
$iteration_count_log2 = 8;
$this->iteration_count_log2 = $iteration_count_log2;
$this->portable_hashes = $portable_hashes;
$this->random_state = microtime();
if (function_exists('getmypid'))
$this->random_state .= getmypid();
}
function get_random_bytes($count)
{
$output = '';
if (function_exists('openssl_random_pseudo_bytes')
&& (version_compare(PHP_VERSION, '5.3.4') >= 0 || !defined('OPENSSL_ALGO_SHA256'))) {
$output = openssl_random_pseudo_bytes($count);
}
if (strlen($output) < $count) {
$output = '';
for ($i = 0; $i < $count; $i += 16) {
$this->random_state = md5(microtime() . $this->random_state);
$output .= pack('H*', md5($this->random_state));
}
$output = substr($output, 0, $count);
}
return $output;
}
function encode64($input, $count)
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count)
$value |= ord($input[$i]) << 8;
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count)
break;
if ($i < $count)
$value |= ord($input[$i]) << 16;
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count)
break;
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
function gensalt_private($input)
{
$output = '$P$';
$output .= $this->itoa64[min($this->iteration_count_log2 + ((PHP_VERSION >= '5') ? 5 : 3), 30)];
$output .= $this->encode64($input, 6);
return $output;
}
function crypt_private($password, $setting)
{
$output = '*0';
if (substr($setting, 0, 2) == $output)
$output = '*1';
$id = substr($setting, 0, 3);
# We use "$P$", standard Blowfish is "$2a$".
if ($id != '$P$')
return $output;
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30)
return $output;
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) != 8)
return $output;
# We're kind of forced to use MD5 here since it's the only
# cryptographic primitive available in all versions of PHP
# currently in use. To implement something portable,
# we need something like this.
if (PHP_VERSION >= '5') {
$hash = md5($salt . $password, TRUE);
do {
$hash = md5($hash . $password, TRUE);
} while (--$count);
} else {
$hash = pack('H*', md5($salt . $password));
do {
$hash = pack('H*', md5($hash . $password));
} while (--$count);
}
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
function HashPassword($password)
{
$random = $this->get_random_bytes(6);
$hash = $this->crypt_private($password, $this->gensalt_private($random));
if (strlen($hash) == 34)
return $hash;
# Returning '*' means probably exceeding strlen limit.
return '*';
}
function CheckPassword($password, $stored_hash)
{
$hash = $this->crypt_private($password, $stored_hash);
if ($hash[0] == '*')
$hash = $stored_hash;
return $hash == $stored_hash;
}
}
?>
这个类包含了几个关键的方法:
PasswordHash()
: 构造函数,初始化一些变量,例如itoa64
、iteration_count_log2
和portable_hashes
。get_random_bytes()
: 生成随机字节。这个函数会尝试使用openssl_random_pseudo_bytes()
函数生成随机字节,如果失败,则使用 MD5 算法生成。encode64()
: 将二进制数据编码成 base64 格式的字符串。gensalt_private()
: 生成 salt。Salt 是一个随机字符串,用于增加密码的安全性。crypt_private()
: 对密码进行加密。这个函数使用 MD5 算法对密码进行多次哈希,并加入 salt。HashPassword()
: 对密码进行哈希,并返回哈希后的字符串。CheckPassword()
: 验证密码是否正确。
第三章:密码哈希流程详解
现在,咱们来详细分析一下 wp_hash_password()
函数是如何对密码进行哈希的。
-
生成 salt:
gensalt_private()
函数会生成一个随机的 salt。Salt 的长度是 8 个字符,由get_random_bytes()
函数生成。function gensalt_private($input) { $output = '$P$'; $output .= $this->itoa64[min($this->iteration_count_log2 + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; $output .= $this->encode64($input, 6); return $output; }
$P$
:这是一个标识符,表示使用了phpass
库进行加密。$this->itoa64[min($this->iteration_count_log2 + ((PHP_VERSION >= '5') ? 5 : 3), 30)]
:这一段是根据迭代次数(iteration_count_log2
)确定加密强度,并从$this->itoa64
字符串中取出一个字符。itoa64
是一个包含 64 个字符的字符串,用于 base64 编码。$this->encode64($input, 6)
:将 6 个字节的随机数($input
)进行 base64 编码,并添加到 salt 中。
-
密码哈希:
crypt_private()
函数会使用 MD5 算法对密码进行多次哈希,并加入 salt。function crypt_private($password, $setting) { $output = '*0'; if (substr($setting, 0, 2) == $output) $output = '*1'; $id = substr($setting, 0, 3); # We use "$P$", standard Blowfish is "$2a$". if ($id != '$P$') return $output; $count_log2 = strpos($this->itoa64, $setting[3]); if ($count_log2 < 7 || $count_log2 > 30) return $output; $count = 1 << $count_log2; $salt = substr($setting, 4, 8); if (strlen($salt) != 8) return $output; # We're kind of forced to use MD5 here since it's the only # cryptographic primitive available in all versions of PHP # currently in use. To implement something portable, # we need something like this. if (PHP_VERSION >= '5') { $hash = md5($salt . $password, TRUE); do { $hash = md5($hash . $password, TRUE); } while (--$count); } else { $hash = pack('H*', md5($salt . $password)); do { $hash = pack('H*', md5($hash . $password)); } while (--$count); } $output = substr($setting, 0, 12); $output .= $this->encode64($hash, 16); return $output; }
$setting
: 这个参数包含了 salt 和迭代次数的信息。$count
: 迭代次数,由 salt 中的信息决定。- 循环: 循环执行 MD5 哈希,次数由
$count
决定。每次哈希都将上次的哈希结果和密码连接起来,再次进行哈希。 $output
: 最终的哈希结果,包含了 salt 和哈希后的密码。
-
返回哈希后的密码:
HashPassword()
函数会返回哈希后的密码。function HashPassword($password) { $random = $this->get_random_bytes(6); $hash = $this->crypt_private($password, $this->gensalt_private($random)); if (strlen($hash) == 34) return $hash; # Returning '*' means probably exceeding strlen limit. return '*'; }
第四章:密码验证流程详解
当用户登录时,WordPress 会使用 wp_check_password()
函数来验证用户输入的密码是否正确。wp_check_password()
函数会调用 PasswordHash
类的 CheckPassword()
方法来进行验证。
function CheckPassword($password, $stored_hash)
{
$hash = $this->crypt_private($password, $stored_hash);
if ($hash[0] == '*')
$hash = $stored_hash;
return $hash == $stored_hash;
}
$password
: 用户输入的密码。$stored_hash
: 存储在数据库中的哈希密码。crypt_private($password, $stored_hash)
: 使用用户输入的密码和存储的哈希密码中的 salt,重新计算哈希值。$hash[0] == '*'
:如果计算出的哈希值以*
开头,表示哈希过程中出现了错误,这时直接使用存储的哈希值进行比较。return $hash == $stored_hash
: 比较计算出的哈希值和存储的哈希值是否相等。如果相等,则表示密码正确。
第五章:为什么使用 phpass
?
你可能会问,为什么 WordPress 要使用 phpass
库来加密密码呢?原因如下:
- 可移植性:
phpass
库可以在不同的 PHP 环境下运行,不需要依赖特定的 PHP 扩展。这使得 WordPress 可以运行在各种不同的服务器上。 - 安全性:
phpass
库使用了 bcrypt 算法(如果可用)或者 MD5 算法,并加入了 salt,可以有效防止彩虹表攻击。 - 成熟稳定:
phpass
库是一个成熟稳定的库,经过了时间的考验。
第六章:安全性考量和改进空间
虽然 phpass
库在当时是一个不错的选择,但现在看来,它也存在一些局限性:
- MD5 算法:
phpass
库在 PHP 5.3 之前主要使用 MD5 算法,而 MD5 算法已经被证明是不安全的。虽然phpass
加入了 salt,但是仍然存在被破解的风险。 - bcrypt 支持: 虽然
phpass
库支持 bcrypt 算法,但是需要 PHP 安装了crypt()
函数,并且操作系统支持 bcrypt 算法。这在一些老旧的服务器上可能无法实现。
为了提高密码的安全性,现在更好的选择是使用 PHP 内置的 password_hash()
和 password_verify()
函数。这两个函数使用了 bcrypt 算法(或者 Argon2i 算法,如果可用),并且提供了更高级的安全性。
WordPress 也在不断改进密码加密机制。在 WordPress 4.4 版本中,引入了 wp_check_password_compat()
函数,用于兼容旧的密码哈希方式。在 WordPress 4.4 之后的版本中,建议使用 wp_hash_password()
函数对密码进行加密,并使用 wp_check_password()
函数进行验证。这两个函数会自动选择最安全的密码哈希算法。
第七章:总结
今天,咱们一起深入学习了 WordPress 中 wp_hash_password()
函数的源码,了解了它是如何使用 phpass
库对密码进行加密的。
总而言之,wp_hash_password()
函数通过 phpass
库,为 WordPress 用户的密码安全提供了保障。当然,随着技术的不断发展,密码安全也面临着新的挑战。WordPress 也在不断改进密码加密机制,以确保用户的密码安全。
课堂小结:
功能点 | 描述 |
---|---|
加密算法 | 主要使用 MD5 (在 PHP 5.3 之前) 或 bcrypt (如果系统支持),并加入 Salt |
Salt | 随机生成的字符串,用于增加密码的安全性,防止彩虹表攻击 |
迭代次数 | 对密码进行多次哈希的次数,增加破解难度 |
兼容性 | phpass 库具有良好的可移植性,可以在不同的 PHP 环境下运行 |
替代方案 | 现在更推荐使用 PHP 内置的 password_hash() 和 password_verify() 函数,它们使用了更安全的 bcrypt 或 Argon2i 算法 |
WordPress 更新 | WordPress 也在不断改进密码加密机制,建议使用 wp_hash_password() 和 wp_check_password() 函数,它们会自动选择最安全的密码哈希算法 |
希望今天的讲座对大家有所帮助。记住,密码安全,马虎不得!
下次再见!