PHP 日本研討會 2024

crypt

(PHP 4, PHP 5, PHP 7, PHP 8)

crypt單向字串雜湊

警告

此函式(目前)並非二進位安全!

說明

crypt(#[\SensitiveParameter] string $string, string $salt): string

crypt() 將使用標準 Unix DES 為基礎的演算法或替代演算法來傳回雜湊字串。password_verify()crypt() 相容。因此,由 crypt() 建立的密碼雜湊可與 password_verify() 一起使用。

在 PHP 8.0.0 之前,salt 參數是可選的。然而,crypt() 在沒有 salt 的情況下會建立弱雜湊,並且會產生 E_NOTICE 錯誤。請務必指定足夠強的 salt 以獲得更好的安全性。

password_hash() 使用強雜湊,產生強 salt,並自動套用適當的輪次。password_hash() 是一個簡單的 crypt() 包裝函式,並且與現有的密碼雜湊相容。建議使用 password_hash()

雜湊型別是由 salt 引數觸發。如果沒有提供 salt,PHP 將自動產生標準的兩個字元 (DES) salt,或十二個字元 (MD5),取決於 MD5 crypt() 的可用性。PHP 設定一個名為 CRYPT_SALT_LENGTH 的常數,指示可用雜湊允許的最長有效 salt。

基於標準 DES 的 crypt() 會將 salt 作為輸出的前兩個字元傳回。它也只使用 string 的前八個字元,因此以相同八個字元開頭的較長字串會產生相同的結果(當使用相同的 salt 時)。

支援下列雜湊型別

  • CRYPT_STD_DES - 基於標準 DES 的雜湊,具有來自字母 "./0-9A-Za-z" 的兩個字元 salt。在 salt 中使用無效字元會導致 crypt() 失敗。
  • CRYPT_EXT_DES - 基於擴充 DES 的雜湊。"salt" 是一個 9 字元字串,由一個底線後接 4 個字元的迭代計數和 4 個字元的 salt 組成。這些 4 字元字串的每一個都會編碼 24 位元,最低有效字元優先。值 063 會編碼為 ./0-9A-Za-z。在 salt 中使用無效字元會導致 crypt() 失敗。
  • CRYPT_MD5 - 使用以 $1$ 開頭的十二個字元 salt 進行 MD5 雜湊
  • CRYPT_BLOWFISH - Blowfish 雜湊,salt 如下:「$2a$」、「$2x$」或「$2y$」、兩位數的成本參數、「$」和來自字母 "./0-9A-Za-z" 的 22 個字元。在 salt 中使用此範圍以外的字元會導致 crypt() 傳回零長度字串。兩位數的成本參數是基於底層 Blowfish 的雜湊演算法的迭代計數的以 2 為底的對數,並且必須在 04-31 的範圍內,超出此範圍的值會導致 crypt() 失敗。「$2x$」雜湊可能較弱;「$2a$」雜湊相容並能減輕此弱點。對於新的雜湊,應使用「$2y$」。
  • CRYPT_SHA256 - SHA-256 雜湊,帶有以 $5$ 作為前綴的十六個字元 salt。如果 salt 字串以 'rounds=<N>$' 開頭,則 N 的數值會用來指示應執行雜湊迴圈的次數,與 Blowfish 的成本參數非常相似。預設的輪次數為 5000,最小值為 1000,最大值為 999,999,999。超出此範圍的任何 N 選取都會截斷至最接近的限制。
  • CRYPT_SHA512 - SHA-512 雜湊,帶有以 $6$ 作為前綴的十六個字元 salt。如果 salt 字串以 'rounds=<N>$' 開頭,則 N 的數值會用來指示應執行雜湊迴圈的次數,與 Blowfish 的成本參數非常相似。預設的輪次數為 5000,最小值為 1000,最大值為 999,999,999。超出此範圍的任何 N 選取都會截斷至最接近的限制。

參數

string

要雜湊的字串。

注意

使用 CRYPT_BLOWFISH 演算法將會導致 string 參數截斷為最大長度 72 個位元組。

salt

用來進行雜湊的 salt 字串。如果未提供,則行為由演算法實作定義,並可能導致非預期的結果。

傳回值

傳回雜湊字串,或短於 13 個字元的字串,並保證在失敗時與 salt 不同。

警告

在驗證密碼時,應使用不易受時間攻擊影響的字串比較函式,將 crypt() 的輸出與先前已知的雜湊進行比較。PHP 提供 hash_equals() 來達到此目的。

變更記錄

版本 說明
8.0.0 salt 不再是可選的。

範例

範例 1 crypt() 範例

<?php
$user_input
= 'rasmuslerdorf';
$hashed_password = '$6$rounds=1000000$NJy4rIPjpOaU$0ACEYGg/aKCY3v8O8AfyiO7CTfZQ8/W231Qfh2tRLmfdvFD6XfHk12u6hMr9cYIA4hnpjLNSTRtUwYr9km9Ij/';

// 以與非 PHP 軟體相容的方式驗證現有的 crypt() 雜湊。
if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) {
echo
"密碼已驗證!";
}
?>

註解

注意 因為 crypt() 使用單向演算法,所以沒有解密函式。

參見

  • hash_equals() - 時間攻擊安全字串比較
  • password_hash() - 建立密碼雜湊
  • 請參閱您的 crypt 函式的 Unix man 手冊以取得更多資訊

新增註解

使用者提供的註解 5 則註解

69
bob dot orr at mailinator dot com
9 年前
截至 2015 年 2 月,此留言頁面的 #2 則留言已存在 9 年,並推薦使用 phpass。我已獨立對此產品進行安全性審核,雖然它持續被推薦用於密碼安全,但實際上它並不安全,不應使用。它已多年未更新(仍為 v0.3 版本),並且有更新的替代方案,例如使用更新的內建 PHP `password_hash()` 函式,這些方案更好。請各位花一點時間確認我所說的是否正確(即自行檢閱 phpass 程式碼),然後點擊向下箭頭將 phpass 留言沉到最底部。這樣做將有助於提高整個網際網路的安全性。

針對想了解詳情的人:`phpass` 的原始碼中,使用 `md5()` 與 `microtime()` 是一種備用方案。它並非終止,而是繼續執行程式碼。作者試圖在所有地方運作的意圖值得讚賞,但當涉及到應用程式安全性時,這種立場實際上會適得其反。在安全性的考量下,唯一正確的做法是終止應用程式,而不是退回到可能被利用的弱點(通常是透過強制發生較弱的情況)。
31
Marten Jacobs
10 年前
據我所知,Blowfish 通常被視為一種安全的雜湊演算法,即使在企業使用中也是如此(如果我說錯了請指正)。因此,我建立了函式來使用此演算法建立和檢查安全的密碼雜湊,並使用(也被認為是密碼學安全的)`openssl_random_pseudo_bytes` 函式來產生鹽。

<?php
/*
* 為給定的密碼產生安全雜湊。成本會傳遞給 Blowfish 演算法。
* 請查看 PHP 手冊的 crypt 頁面以取得更多關於此設定的資訊。
*/
function generate_hash($password, $cost=11){
/* 為了產生鹽,首先產生足夠的隨機位元組。因為
* base64 對每 6 個位元返回一個字元,我們應該產生
* 至少 22*6/8=16.5 個位元組,因此我們產生 17 個。然後我們取得前
* 22 個 base64 字元
*/
$salt=substr(base64_encode(openssl_random_pseudo_bytes(17)),0,22);
/* 因為 Blowfish 使用 ./A-Za-z0-9 字母作為鹽,我們必須
* 將 base64 字串中的任何 '+' 取代為 '.'。我們不必對 '=' 做任何處理,
* 因為它只會在 b64 字串被填充時出現,這總是在前 22 個字元之後。
*/
$salt=str_replace("+",".",$salt);
/* 接下來,建立一個將傳遞給 crypt 的字串,其中包含所有
* 設定,並以錢字符號分隔
*/
$param='$'.implode('$',array(
"2y", //選擇 Blowfish 的最安全版本(>=PHP 5.3.7)
str_pad($cost,2,"0",STR_PAD_LEFT), //以兩位數新增成本
$salt //新增鹽
));

//現在執行實際的雜湊
return crypt($password,$param);
}

/*
* 根據 generate_hash 函式產生的雜湊檢查密碼。
*/
function validate_pw($password, $hash){
/* 如果傳遞相同的密碼,使用現有的雜湊作為選項參數重新產生雜湊
* 應該會產生相同的雜湊。
*/
return crypt($password, $hash)==$hash;
}
?>
7
kaminski at istori dot com
13 年前
以下是為 CRYPT_BLOWFISH 雜湊類型產生虛擬隨機鹽的表達式

<?php $salt = substr(str_replace('+', '.', base64_encode(pack('N4', mt_rand(), mt_rand(), mt_rand(), mt_rand()))), 0, 22); ?>

它旨在用於 `mt_getrandmax()` == 2147483647 的系統。

建立的鹽長度將為 128 位元,填充為 132 位元,然後以 22 個 base64 字元表示。(CRYPT_BLOWFISH 僅使用 128 位元的鹽,即使 22 個 base64 字元中有 132 位元。如果您檢查 CRYPT_BLOWFISH 的輸入和輸出,您會看到它忽略輸入的最後四個位元,並在輸出時將它們設定為零。)

請注意,`mt_rand()` 返回的四個 32 位元雙字的最高有效位將始終為零(因為 mt_getrandmax == 2^31),因此只有 124 位元的 128 位元是虛擬隨機的。我發現這對於我的應用程式來說是可以接受的。
5
steve at tobtu dot com
11 年前
若要產生鹽,請使用 `mcrypt_create_iv()` 而不是 `mt_rand()`,因為無論您呼叫 `mt_rand()` 多少次,它最多只有 32 位元的熵。您大約在 2^16 個使用者後就會開始看到鹽衝突。`mt_rand()` 的種子設定不佳,因此應該會更早發生。

對於 bcrypt,這實際上會產生一個 128 位元的鹽
<?php $salt = strtr(base64_encode(mcrypt_create_iv(16, MCRYPT_DEV_URANDOM)), '+', '.'); ?>

*** 雞毛蒜皮的爭論 ***
22 字元鹽中的最後一個字元是 2 位元。
`base64_encode()` 將會有這四個字元 "AQgw"
bcrypt 將會有這四個字元 ".Oeu"

您不需要進行完整的翻譯,因為它們會「四捨五入」為不同的字元
echo crypt('', '$2y$05$.....................A') . "\n";
echo crypt('', '$2y$05$.....................Q') . "\n";
echo crypt('', '$2y$05$.....................g') . "\n";
echo crypt('', '$2y$05$.....................w') . "\n";

$2y$05$......................J2ihDv8vVf7QZ9BsaRrKyqs2tkn55Yq
$2y$05$.....................O/jw2XygQa2.LrIT7CFCBQowLowDP6Y.
$2y$05$.....................eDOx4wMcy7WU.kE21W6nJfdMimsBE3V6
$2y$05$.....................uMMcgjnOELIa6oydRivPkiMrBG8.aFp.
-3
jette at nerdgirl dot dk
11 年前
`crypt()` 函式無法正確處理加號。因此,如果您在登入函式中使用 crypt,請先對密碼使用 `urlencode`,以確保登入程序可以處理任何字元

<?php
$user_input
= '12+#æ345';
$pass = urlencode($user_input));
$pass_crypt = crypt($pass);

if (
$pass_crypt == crypt($pass, $pass_crypt)) {
echo
"Success! Valid password";
} else {
echo
"Invalid password";
}
?>
To Top