Search code examples
phpsymfonypassword-hashsymfony-security

How Symfony 5 stores salt for argon2?


How symfony stores salt string for argon2? Salt in argon is mandatory whent encoding password but no salt string is stored in User Entity. Function in password encoder supports salt as argument but is null and never used

/vendor/symfony/security-core/Encoder/SodiumPasswordEncoder.php

public function encodePassword(string $raw, ?string $salt): string
{
    if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) {
        throw new BadCredentialsException('Invalid password.');
    }

    if (\function_exists('sodium_crypto_pwhash_str')) {
        return sodium_crypto_pwhash_str($raw, $this->opsLimit, $this->memLimit);
    }

    if (\extension_loaded('libsodium')) {
        return \Sodium\crypto_pwhash_str($raw, $this->opsLimit, $this->memLimit);
    }

    throw new LogicException('Libsodium is not available. You should either install the sodium extension, upgrade to PHP 7.2+ or use a different encoder.');
}

Solution

  • Salt is stored directly on the hashed password, there is no need of using a separate field to store the salt.

    The $salt parameter is not used, because on each call to sodium_crypto_pwhash_str a random salt will be generated, as explained in the docs:

    Uses a CPU- and memory-hard hash algorithm along with a randomly-generated salt, and memory and CPU limits to generate an ASCII-encoded hash suitable for password storage.

    The return value will have the hashed password, the used algorithm, the salt, the memory cost, the time cost, etc. Everything that's needed to rehash and verify a user supplied password, and that's what's stored on UserInterface::$password.

    E.g., calling:

    sodium_crypto_pwhash_str('secret_string',
        SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
        SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
    );
    

    Would return something like this:

    $argon2i$v=19$m=32768,t=4,p=1$smna9HfWD+caJJakZiekyQ$qbflsyuP3txLRgsGIt1alcv7HmYjfiMPanYtDU0LtCA
    

    The different parts of the response are separated by the $ character, and are as follows:

    1. argon2i: algorithm used
    2. v=19: version
    3. m=32768,t=4,p=1: algorithm options (memory cost, time cost, and threads to be used)
    4. smna9HfWD+caJJakZiekyQ This is the automatically generated salt.
    5. qbflsyuP3txLRgsGIt1alcv7HmYjfiMPanYtDU0LtCA. The actual hash.

    This is what's stored on UserInterface::$password, which as you can see has all the information required to verify a hash, including the salt. Storing the salt in a separate field is deprecated, and the field is only kept for backwards-compatibility.

    Anyone using the above method or the recommended password_hash would have all their needs covered without a dedicated salt field.