Derive a 32-byte key from a password deterministically in PHP

前端 未结 3 696
[愿得一人]
[愿得一人] 2021-01-18 16:11

Today I learned that \"password\" tends to mean a memorizable string of an arbitrary number of characters, while \"key\" means a highly random string of bi

相关标签:
3条回答
  • 2021-01-18 16:43

    If you wish to have them re-send their password every time you want to decrypt or encrypt the stored strings, you will have to use a consistent password hash and store the salt and iterations somewhere.

    If you use the password_hash function, you'll never end up with the same value because of the randomly generated salt.

    >>> password_hash('abc', PASSWORD_BCRYPT)
    => "$2y$10$xR8tZQd0ljF5Ks3QrQt7i.vAbv.xVUc97uh.fX4w0mi/A647HlEWS"
    >>> password_hash('abc', PASSWORD_BCRYPT)
    => "$2y$10$KzZWeg.o/4TyJVryWrz/oeWQ6VGj0JnPDW.d.Cp0svu8k6qKBcbWu"
    

    You can pass a salt through the options but this is deprecated through password_hash, so I'd recommend you stick with your first solution.

    You don't need to use the same salt for every person, you can generate a random salt and store that somewhere, such as the users table.

    Keep in mind, with this type of key derivation, you'll need to re-encrypt all of the values every time the user changes their password.

    0 讨论(0)
  • 2021-01-18 16:46

    For hash-pbkdf2 you say:

    "The following approach works but ignores the instruction of "[The salt] should be generated randomly"

    Well, the fix to that is to do generate the salt randomly, and store it with the ciphertext. See this question for methods on how to generate secure random bytes within PHP. The output can then be used as key to encrypt; of course the key will always be regenerated using the stored salt and memorized password, and doesn't need to be stored. Note that keys consist of raw bytes; it's probably best to retrieve a raw key from hash-pbkdf2 (the last parameter).

    Note that the iteration count should be as high as possible. Normally 100,000 or so is considered optimal nowadays, but the higher the more secure. It takes about as much time for an attacker to calculate the resulting key for each password, and as passwords only contain 30 to 60 bits (for good passwords) it really helps against dictionary attacks.

    0 讨论(0)
  • 2021-01-18 17:02

    Here is my updated function

    But I'd still appreciate answers from experts since this feels very unofficial and home-grown and makes me wonder whether it's breaking any "best practices" of security.

    I was surprised not to find a simple function built into PHP.

    /**
     * It seems like we just need a way of getting a 32-byte key when all we have is a human-memorizable password and a salt for that user. But this function feels home-grown; what is the most secure way to do PBE (password-based encryption)?
     * 
     * @param string $password
     * @param string $salt      Since this function must be deterministic (return a value consistently based on the inputs), it must accept a salt as an argument rather than generate a random salt every time. Storing a different salt for each user improves security.
     * @param int $length
     * @return string
     */
    public static function deriveKey($password, $salt, $length = self::KEY_BYTES) {
        $iterations = max([intval(config('hashing.bcrypt.rounds')), 15]);
        $chars = 2 * $length; //In hex, a byte is always expressed as 2 characters. See more comments below and https://stackoverflow.com/a/43132091/.
        $rawOutput = false; //Default is false. When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits. Hexit = hexadecimal digit (like "bit" = binary digit). There are 16 hexits: the numbers 0 to 9 and the letters A to F.
        $key = hash_pbkdf2('sha256', $password, $salt, $iterations, $chars, $rawOutput); //A sha256 is 256 bits long (32 bytes), but the raw_output argument will determine how many characters the result has. https://stackoverflow.com/a/2241014/ and https://crypto.stackexchange.com/q/34995/
        return $key;
    }
    

    I wonder if Halite or Libsodium-php offer this kind of function.

    It seems like Libsodium has a crypto_pwhash function that probably is what I'm looking for (and uses Argon2).

    0 讨论(0)
提交回复
热议问题