在使用 hash_pbkdf2 进行密码加密时,salt 是保障安全性的关键组成部分。很多开发者虽然知道 hash_pbkdf2 是一个基于密码的密钥派生函数,但却忽视了如何正确、安全地生成和使用 salt。本文将深入讲解 salt 的作用、常见误区,以及在 PHP 中生成安全 salt 的推荐做法。
salt 的作用是防止彩虹表攻击和相同密码生成相同哈希的风险。没有 salt,攻击者只需要一张事先生成好的密码-哈希表,就可以反推出明文密码。而 salt 的加入使得每次加密结果都独一无二,即使两个用户设置了相同的密码,其哈希值也完全不同。
PHP 的 hash_pbkdf2 函数原型如下:
<span><span><span class="hljs-keyword">string</span></span><span> </span><span><span class="hljs-title function_ invoke__">hash_pbkdf2</span></span><span> (
</span><span><span class="hljs-keyword">string</span></span><span> </span><span><span class="hljs-variable">$algo</span></span><span>,
</span><span><span class="hljs-keyword">string</span></span><span> </span><span><span class="hljs-variable">$password</span></span><span>,
</span><span><span class="hljs-keyword">string</span></span><span> </span><span><span class="hljs-variable">$salt</span></span><span>,
</span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$iterations</span></span><span>,
</span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$length</span></span><span> = </span><span><span class="hljs-number">0</span></span><span>,
</span><span><span class="hljs-keyword">bool</span></span><span> </span><span><span class="hljs-variable">$binary</span></span><span> = </span><span><span class="hljs-literal">false</span></span><span>
)
</span></span>
其中 $salt 参数用于增加哈希的唯一性,因此其生成方式至关重要。
使用固定字符串作为 salt:
<span><span><span class="hljs-variable">$salt</span></span><span> = </span><span><span class="hljs-string">'123456'</span></span><span>;
</span></span>
这是非常危险的行为。固定 salt 意味着所有用户的哈希值很可能重复。
使用简单函数生成:
比如 uniqid()、mt_rand() 等函数,它们要么不够随机,要么可以被预测。
<span><span><span class="hljs-variable">$salt</span></span><span> = </span><span><span class="hljs-title function_ invoke__">uniqid</span></span><span>();
</span></span>
虽然看似唯一,但缺乏足够的熵,不推荐用于安全目的。
推荐使用 PHP 提供的加密安全级别的函数来生成 salt:
<span><span><span class="hljs-variable">$salt</span></span><span> = </span><span><span class="hljs-title function_ invoke__">random_bytes</span></span><span>(</span><span><span class="hljs-number">16</span></span><span>); </span><span><span class="hljs-comment">// 16 字节 = 128 位</span></span><span>
</span></span>
如果你需要将其存储为字符串形式(例如保存到数据库中),可以使用 bin2hex() 或 base64_encode() 编码:
<span><span><span class="hljs-variable">$salt</span></span><span> = </span><span><span class="hljs-title function_ invoke__">bin2hex</span></span><span>(</span><span><span class="hljs-title function_ invoke__">random_bytes</span></span><span>(</span><span><span class="hljs-number">16</span></span><span>)); </span><span><span class="hljs-comment">// 32 个字符的十六进制字符串</span></span><span>
</span></span>
以下是一个完整的例子,演示如何使用 hash_pbkdf2 安全加密密码:
<span><span><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span> </span><span><span class="hljs-title">hashPassword</span></span><span>(</span><span><span class="hljs-params"><span class="hljs-variable">$password</span></span></span><span>) {
</span><span><span class="hljs-variable">$salt</span></span><span> = </span><span><span class="hljs-title function_ invoke__">bin2hex</span></span><span>(</span><span><span class="hljs-title function_ invoke__">random_bytes</span></span><span>(</span><span><span class="hljs-number">16</span></span><span>)); </span><span><span class="hljs-comment">// 生成安全的 salt</span></span><span>
</span><span><span class="hljs-variable">$iterations</span></span><span> = </span><span><span class="hljs-number">100000</span></span><span>;
</span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>;
</span><span><span class="hljs-variable">$length</span></span><span> = </span><span><span class="hljs-number">64</span></span><span>;
</span><span><span class="hljs-variable">$hash</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_pbkdf2</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, </span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-variable">$salt</span></span><span>, </span><span><span class="hljs-variable">$iterations</span></span><span>, </span><span><span class="hljs-variable">$length</span></span><span>, </span><span><span class="hljs-literal">false</span></span><span>);
</span><span><span class="hljs-keyword">return</span></span><span> [
</span><span><span class="hljs-string">'salt'</span></span><span> => </span><span><span class="hljs-variable">$salt</span></span><span>,
</span><span><span class="hljs-string">'hash'</span></span><span> => </span><span><span class="hljs-variable">$hash</span></span><span>,
</span><span><span class="hljs-string">'algo'</span></span><span> => </span><span><span class="hljs-variable">$algo</span></span><span>,
</span><span><span class="hljs-string">'iterations'</span></span><span> => </span><span><span class="hljs-variable">$iterations</span></span><span>
];
}
</span><span><span class="hljs-comment">// 示例用法</span></span><span>
</span><span><span class="hljs-variable">$result</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hashPassword</span></span><span>(</span><span><span class="hljs-string">'MySecretPassword'</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">print_r</span></span><span>(</span><span><span class="hljs-variable">$result</span></span><span>);
</span></span>
验证密码时,需要从数据库中读取出原来的 salt、iterations 和 algo,再用相同方式重新生成哈希进行比较:
<span><span><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span> </span><span><span class="hljs-title">verifyPassword</span></span><span>(</span><span><span class="hljs-params"><span class="hljs-variable">$password</span></span></span><span>, </span><span><span class="hljs-variable">$storedHash</span></span><span>, </span><span><span class="hljs-variable">$salt</span></span><span>, </span><span><span class="hljs-variable">$iterations</span></span><span>, </span><span><span class="hljs-variable">$algo</span></span><span> = </span><span><span class="hljs-string">'sha256'</span></span><span>, </span><span><span class="hljs-variable">$length</span></span><span> = </span><span><span class="hljs-number">64</span></span><span>) {
</span><span><span class="hljs-variable">$hash</span></span><span> = </span><span><span class="hljs-title function_ invoke__">hash_pbkdf2</span></span><span>(</span><span><span class="hljs-variable">$algo</span></span><span>, </span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-variable">$salt</span></span><span>, </span><span><span class="hljs-variable">$iterations</span></span><span>, </span><span><span class="hljs-variable">$length</span></span><span>, </span><span><span class="hljs-literal">false</span></span><span>);
</span><span><span class="hljs-keyword">return</span></span><span> </span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$storedHash</span></span><span>, </span><span><span class="hljs-variable">$hash</span></span><span>);
}
</span></span>
hash_equals 可以防止时序攻击,是进行哈希比较时的安全选择。
安全地使用 hash_pbkdf2,关键在于生成强随机的 salt 和设置足够的迭代次数。切忌使用静态或低熵的 salt,否则将破坏整个加密流程的安全性。在密码安全领域,没有所谓的“凑合能用”,每一个细节都值得认真对待。希望本文能帮助你理解 hash_pbkdf2 中 salt 的正确生成方式,从而构建更加安全的 PHP 应用。