在PHP 中, hash_equals函數是用於安全地比較兩個哈希字符串是否相等的函數。它的設計初衷是為了避免時間攻擊(timing attacks),即黑客通過分析程序響應時間的微小差異來推測密碼是否正確。然而,在實際使用中,開發者經常會犯一些常見錯誤,導致hash_equals函數未能達到預期的安全效果。本文將詳細討論這些錯誤,並提供解決方法。
hash_equals的目的在於防止通過字符串比較的時間差洩露密碼信息。與傳統的==或===操作符不同, hash_equals在比較過程中不依賴字符串的長度或內容,因此可以避免時間攻擊。
常見的錯誤之一是將hash_equals與常規的字符串比較操作混淆。例如:
<span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$password</span></span><span> == </span><span><span class="hljs-variable">$storedPasswordHash</span></span><span>) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
}
</span></span>
在這裡,雖然是比較哈希值,但PHP 使用==或===進行普通的比較操作,可能會根據哈希值的內容或長度做不同的優化,從而導致時間洩露。
解決方法:
始終使用hash_equals進行哈希值的比較:
<span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-variable">$storedPasswordHash</span></span><span>)) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
}
</span></span>
另一個常見錯誤是在比對原文密碼和存儲的哈希值時使用hash_equals 。這是一種常見的誤解,開發者可能會認為可以直接將密碼明文與哈希值進行比較。
<span><span><span class="hljs-comment">// 錯誤示範</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-title function_ invoke__">password_hash</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>, PASSWORD_BCRYPT))) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
}
</span></span>
這段代碼是錯誤的,因為password_hash返回的是一個哈希值,而hash_equals只能用於比較兩個哈希字符串,而不能直接比較明文和哈希。
解決方法:
應該先通過適當的函數(如password_verify )來驗證密碼明文和哈希值是否匹配。 password_verify是專為這一目的設計的函數:
<span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">password_verify</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-variable">$storedPasswordHash</span></span><span>)) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
}
</span></span>
hash_equals在執行比較時,除了要比較哈希值本身的內容,還會檢查兩個哈希值的長度是否一致。如果長度不一致, hash_equals會立即返回false ,避免繼續執行不必要的計算。因此,開發者在某些情況下可能沒有意識到傳入的哈希值在長度上有所不同,從而造成錯誤。
例如,如果你在存儲哈希時對其做了某些處理(如截斷或格式化),哈希的長度可能會變化,導致比較失敗。
解決方法:
確保在存儲和比較哈希時使用一致的格式,避免不必要的修改。 password_hash函數會返回一個標準的哈希格式,盡量避免對其進行人工操作。
hash_equals只能用於比較相同類型的哈希值。如果在密碼哈希時使用了不同的算法(例如使用md5和sha256 ),則hash_equals比較會失效,因為它無法處理不同哈希算法之間的比較。
<span><span><span class="hljs-comment">// 錯誤示範:md5 和 sha256 哈希比較</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-title function_ invoke__">md5</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>), </span><span><span class="hljs-title function_ invoke__">sha256</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>))) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
}
</span></span>
解決方法:
使用相同的哈希算法進行密碼存儲和比較。如果你使用password_hash函數來生成哈希值,它會使用適當的哈希算法(通常是BCRYPT 或Argon2),你只需要通過password_verify進行驗證即可。
hash_equals返回的是一個布爾值,表示兩個哈希值是否相等。如果開發者忽略了對該值的正確處理,可能會導致錯誤的邏輯判斷。
<span><span><span class="hljs-comment">// 錯誤示範:沒有處理返回值</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-variable">$storedPasswordHash</span></span><span>)) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
} </span><span><span class="hljs-keyword">else</span></span><span> {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is incorrect!"</span></span><span>;
}
</span></span>
這裡的錯誤不在於hash_equals ,而是在於沒有在錯誤條件下處理適當的流程。為了確保密碼驗證過程的安全性,需要確保無論密碼是否正確,都能正確反饋。
解決方法:
確保根據hash_equals的返回值做適當的判斷:
<span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">hash_equals</span></span><span>(</span><span><span class="hljs-variable">$password</span></span><span>, </span><span><span class="hljs-variable">$storedPasswordHash</span></span><span>)) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Password is correct!"</span></span><span>;
} </span><span><span class="hljs-keyword">else</span></span><span> {
</span><span><span class="hljs-comment">// 可做更安全的錯誤處理,如日誌記錄</span></span><span>
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Invalid password!"</span></span><span>;
}
</span></span>
hash_equals是一個非常重要的PHP 函數,用於防止時間攻擊,但它並不是萬能的,錯誤的使用方式可能會導致程序的安全漏洞。常見的錯誤包括混淆哈希比較、錯誤地比較明文密碼與哈希值、哈希值長度不一致、使用不同的哈希算法以及忽略返回值等。解決這些錯誤的關鍵是理解hash_equals的使用場景,以及在合適的場景下使用password_verify來驗證密碼的正確性。
通過正確的實現和最佳實踐,可以大大提高密碼驗證過程中的安全性,避免潛在的攻擊風險。