password_verify和doveadm如何在没有盐的情况下验证密码

im currently trying to understand hashes and salts. As i understand it, it should not be possible to verify a password, if i only have the password and the generated hash(that was generated with a random salt ofc). So how can the password_verify function in PHP verify my password, if i dont give it the salt? Is there a hidden variable in the background, that stores it for the php hashing functions?

And if that is the case, how can

doveadm pw -t '{SHA512-CRYPT}$6$myhash...' -p "qwertz"

verify it too, even if i run it on a complety different computer? Thats a tool, that comes with Dovecot(a MDA).

Here is my PHP code, that creates a random salt with 64 chars, combines it with a password, creates a hash and verifies the hash via password_verify().

I just started working on the whole hash/salt/pepper thing today, so there could be a huge flaw in my whole train of thought.

<?php
$password = "qwertz";
$salt = createSalt(64);
$hash = crypt($password, "$6$$salt");

if (password_verify($password, $hash)) {
    echo 'Password is valid!';
} else {
    echo 'Invalid password.';
}


function createSalt($length){
    $chars = "IrhsYyLofUKj4caz0FDBCe2W9NRunTgQvp7qOXmS5GM3EJV6i8tAHdkPbxwl1Z";
    $salt="";
    for($i=0; $i < $length; $i++){
        $newchar = substr($chars, rand(0,strlen($chars)-1),1);
        $salt .= $newchar;
    }
    return $salt;
}
?>

The hash contains several pieces of information. This article explains the format used by Unix but I believe PHP password functions use a similar format (if not the same):

The hash field itself is comprised of three different fields. They are separated by '$' and represent:

  • Some characters which represents the cryptographic hashing mechanism used to generate the actual hash
  • A randomly generated salt to safeguard against rainbow table attacks
  • The hash which results from joining the users password with the stored salt and running it through the hashing mechanism specified in the first field

It can also include the exact per-algorithm options used to generate the hash, such us the algorithmic cost:

var_dump(password_hash('foo', PASSWORD_BCRYPT, [
    'cost' => 8,
]));
string(60) "$2y$08$7Z5bTz7xXnom8QsrbZ7uQetMLxOZ7WjuDkUYRIh73Ffa17GV1Tb7q"

Here $2y$08$ means that Bcrypt with cost 8 was used.

If we use the newer Argon2 available in PHP/7.2 there're even more params:

$argon2i$v=19$m=1024,t=2,p=2$YzJBSzV4TUhkMzc3d3laeg$zqU/1IN0/AogfP4cmSJI1vc8lpXRW9/S0sYY2i2jHT0

Some backgrounds to the answer from @Álvaro González :

PHP manual suggests using "password_hash" instead of "crypt" function through "password_hash" is a "crypt()" wrapper ( Because, it uses a strong hash, generates a strong salt, and applies proper rounds automatically. )

"password_hash()" returns the algorithm, cost, and salt as part of the returned hash. Therefore, all information that's needed to verify the hash is included in it. This allows the "password_verify" function to verify the hash without needing separate storage for the salt or algorithm information. : http://php.net/manual/en/function.password-verify.php

Since, "password_hash" is a wrapper for "crypt", "crypt" also does the same, ie., returns the algorithm, cost, and salt as part of the returned hash. and thus "password_verify" can verify the hash.

Now, please check the answer given by @Álvaro González