public function deriveKey($password, $salt, $keyLength, $iterations = null) { $iterations = $iterations ?: $this->kdfIterations; $key = openssl_pbkdf2($password, $salt, $keyLength, $iterations, 'sha512'); // This is really not ideal, but openssl's PEM key encryption doesn't // work if there are any null bytes in the passphrase. See the doc comment // for Encryptor::generateRandomBytesWithoutNulls() for more detail. return str_replace(hex2bin('00'), hex2bin('FF'), $key); }
/** * Display time to compute increasing numbers of PBKDF2 iterations */ public function benchmark_kdfAction() { $password = openssl_random_pseudo_bytes(15); $salt = openssl_random_pseudo_bytes(32); $iterations = 1000; while (true) { $this->startTimer(); openssl_pbkdf2($password, $salt, 32, $iterations); $time = $this->getElapsed(); printf("%d iterations in %.2fms\n", $iterations, $time); if ($time < 2) { $iterations *= 1.5; } else { break; } } echo "Put your chosen number of KDF iterations in your local config under 'encryption.kdf.iterations'\n"; }
/** * Key derivation wth pbkdf2: http://en.wikipedia.org/wiki/PBKDF2 * @param string $key payload * @param string $salt random string from generate_salt * @param string $length result length * @param string $count iterations * @param string $algo hash algorithm to use */ public static function pbkdf2($key, $salt, $length, $count, $algo) { /* requires PHP >= 5.5 */ if (Hm_Functions::function_exists('openssl_pbkdf2')) { return openssl_pbkdf2($key, $salt, $length, $count, $algo); } /* manual version */ $size = strlen(hash($algo, '', true)); $len = ceil($length / $size); $result = ''; for ($i = 1; $i <= $len; $i++) { $tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true); $res = $tmp; for ($j = 1; $j < $count; $j++) { $tmp = hash_hmac($algo, $tmp, $key, true); $res ^= $tmp; } $result .= $res; } return substr($result, 0, $length); }
<?php // official test vectors var_dump(bin2hex(openssl_pbkdf2('password', 'salt', 20, 1))); var_dump(bin2hex(openssl_pbkdf2('password', 'salt', 20, 2))); var_dump(bin2hex(openssl_pbkdf2('password', 'salt', 20, 4096))); /* really slow but should be: string(40) "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984" var_dump(bin2hex(openssl_pbkdf2('password', 'salt', 20, 16777216))); */ var_dump(bin2hex(openssl_pbkdf2('passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt', 25, 4096))); var_dump(bin2hex(openssl_pbkdf2("password", "salt", 16, 4096)));
/** * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt. * * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt * * This implementation of PBKDF2 was originally created by https://defuse.ca * With improvements by http://www.variations-of-shadow.com * * @param string $password the password * @param string $salt a salt that is unique to the password * @param string $keyLength the length of the derived key in bytes * @param string $count iteration count. Higher is better, but slower. Recommended: At least 1000 * @param string $algorithm the hash algorithm to use. Recommended: SHA256 * @param string $binaryUnsafe if true, the key is returned in raw binary format. Hex encoded otherwise * * @return string the key derived from the password and salt * * @throws \InvalidArgumentException invalid arguments have been passed * @throws HashingException the error occurred while generating the requested hashing algorithm */ public static function pbkdf2($password, $salt, $keyLength, $count, $algorithm = self::SHA1, $binaryUnsafe = false) { if (!is_integer($count) || $count <= 0) { throw new \InvalidArgumentException('The iteration number for the PBKDF2 function must be a positive non-zero integer', 2); } if (!is_integer($keyLength) || $keyLength <= 0) { throw new \InvalidArgumentException('The resulting key length for the PBKDF2 function must be a positive non-zero integer', 2); } if (!is_string($algorithm) || strlen($algorithm) <= 0) { throw new \InvalidArgumentException('The hashing algorithm for the PBKDF2 function must be a non-empty string', 2); } //an algorithm is represented as a string of only lowercase chars $algorithm = strtolower($algorithm); //the raw output of the max legth (beyond the $keyLength algorithm) $output = ''; if (function_exists('openssl_pbkdf2')) { /* execute the native openssl_pbkdf2 */ //check if the algorithm is valid if (!in_array($algorithm, openssl_get_md_methods(true), true)) { throw new HashingException('Invalid algorithm: the choosen algorithm is not valid for the PBKDF2 function', 2); } $output = openssl_pbkdf2($password, $salt, $keyLength, $count, $algorithm); } elseif (function_exists('hash_pbkdf2')) { /* execute the native hash_pbkdf2 */ //check if the algorithm is valid if (!in_array($algorithm, hash_algos(), true)) { throw new HashingException('Invalid algorithm: the choosen algorithm is not valid for the PBKDF2 function', 2); } // The output length is in NIBBLES (4-bits) if $binaryUnsafe is false! if (!$binaryUnsafe) { $keyLength = $keyLength * 2; } return hash_pbkdf2($algorithm, $password, $salt, $count, $keyLength, $binaryUnsafe); } else { /* use an hack to emulate openssl_pbkdf2 */ //check if the algorithm is valid if (!in_array($algorithm, hash_algos(), true)) { throw new HashingException('Invalid algorithm: the choosen algorithm is not valid for the PBKDF2 function', 2); } $hashLength = strlen(hash($algorithm, '', true)); $blockCount = ceil($keyLength / $hashLength); for ($i = 1; $i <= $blockCount; ++$i) { // $i encoded as 4 bytes, big endian. $last = $salt . pack('N', $i); // first iteration $last = $xorsum = hash_hmac($algorithm, $last, $password, true); // perform the other $count - 1 iterations for ($j = 1; $j < $count; ++$j) { $xorsum ^= $last = hash_hmac($algorithm, $last, $password, true); } $output .= $xorsum; } } return $binaryUnsafe ? substr($output, 0, $keyLength) : bin2hex(substr($output, 0, $keyLength)); }