在使用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 應用。