Recentemente, tenho tentado implementar minha própria segurança em um script de log-in que encontrei na Internet. Depois de tentar aprender como fazer meu próprio script para gerar um salt para cada usuário, me deparei compassword_hash
.
Pelo que entendi (com base na leitura desta página ), o sal já é gerado na linha quando você usa password_hash
. Isso é verdade?
Outra pergunta que eu tinha era: não seria inteligente ter 2 sais? Um direto no arquivo e outro no BD? Dessa forma, se alguém comprometer o seu salt no DB, você ainda tem aquele diretamente no arquivo? Eu li aqui que armazenar sais nunca é uma ideia inteligente, mas sempre me confundiu o que as pessoas querem dizer com isso.
php
salt
password-hash
php-password-hash
Josh Potter
fonte
fonte
Respostas:
Usar
password_hash
é a maneira recomendada de armazenar senhas. Não os separe em banco de dados e arquivos.Digamos que temos a seguinte entrada:
$password = $_POST['password'];
Primeiro você hash a senha fazendo o seguinte:
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
Então veja a saída:
var_dump($hashed_password);
Como você pode ver, está hash. (Presumo que você tenha feito essas etapas).
Agora você armazena essa senha em hash em seu banco de dados, garantindo que a coluna de senha seja grande o suficiente para conter o valor em hash (pelo menos 60 caracteres ou mais) . Quando um usuário pede para fazer login, você verifica a entrada de senha com este valor de hash no banco de dados, fazendo o seguinte:
// Query the database for username and password // ... if(password_verify($password, $hashed_password)) { // If the password inputs matched the hashed password in the database // Do something, you know... log them in. } // Else, Redirect them back to the login page.
Referência Oficial
fonte
Sim, você entendeu corretamente, a função password_hash () gerará um salt por conta própria e o incluirá no valor hash resultante. Armazenar o salt no banco de dados é absolutamente correto, ele faz seu trabalho mesmo se conhecido.
// Hash a new password for storing in the database. // The function automatically generates a cryptographically safe salt. $hashToStoreInDb = password_hash($_POST['password'], PASSWORD_DEFAULT); // Check if the hash of the entered login password, matches the stored hash. // The salt and the cost factor will be extracted from $existingHashFromDb. $isPasswordCorrect = password_verify($_POST['password'], $existingHashFromDb);
O segundo sal que você mencionou (aquele armazenado em um arquivo), é na verdade uma pimenta ou uma chave do servidor. Se você adicionar antes de hashing (como o sal), você adiciona uma pimenta. Porém, há uma maneira melhor, você pode primeiro calcular o hash e, em seguida, criptografar (bidirecional) o hash com uma chave do lado do servidor. Isso lhe dá a possibilidade de alterar a chave quando necessário.
Em contraste com o sal, essa chave deve ser mantida em segredo. As pessoas costumam misturar tudo e tentar esconder o sal, mas é melhor deixar o sal fazer o seu trabalho e adicionar o segredo com uma chave.
fonte
Sim, é verdade. Por que você duvida do php faq sobre a função? :)
O resultado da corrida
password_hash()
tem quatro partes:Como você pode ver, o hash faz parte disso.
Claro, você poderia ter um sal adicional para uma camada adicional de segurança, mas honestamente acho que isso é um exagero em um aplicativo php normal. O algoritmo bcrypt padrão é bom, e o blowfish opcional é indiscutivelmente ainda melhor.
fonte
Nunca use md5 () para proteger sua senha, mesmo com sal, é sempre perigoso !!
Proteja sua senha com os algoritmos de hash mais recentes, conforme abaixo.
<?php // Your original Password $password = '121@121'; //PASSWORD_BCRYPT or PASSWORD_DEFAULT use any in the 2nd parameter /* PASSWORD_BCRYPT always results 60 characters long string. PASSWORD_DEFAULT capacity is beyond 60 characters */ $password_encrypted = password_hash($password, PASSWORD_BCRYPT);
Para correspondência com a senha criptografada do banco de dados e a senha inserida pelo usuário, use a função abaixo.
<?php if (password_verify($password_inputted_by_user, $password_encrypted)) { // Success! echo 'Password Matches'; }else { // Invalid credentials echo 'Password Mismatch'; }
Se você quiser usar seu próprio salt, use sua função gerada customizada para o mesmo, apenas siga abaixo, mas eu não recomendo isso porque é considerado obsoleto nas versões mais recentes do PHP.
Leia sobre password_hash () antes de usar o código abaixo.
<?php $options = [ 'salt' => your_custom_function_for_salt(), //write your own code to generate a suitable & secured salt 'cost' => 12 // the default cost is 10 ]; $hash = password_hash($your_password, PASSWORD_DEFAULT, $options);
fonte
Há uma falta distinta de discussão sobre compatibilidade com versões anteriores e posteriores que está embutida nas funções de senha do PHP. Notavelmente:
crypt()
e são inerentemente compatíveis com versões anteriores comcrypt()
hashes -format, mesmo que usem algoritmos de hash obsoletos e / ou inseguros.password_needs_rehash()
um pouco de lógica em seu fluxo de trabalho de autenticação pode manter seus hashes atualizados com algoritmos atuais e futuros com potencialmente zero alterações futuras no fluxo de trabalho. Nota: Qualquer string que não corresponda ao algoritmo especificado será sinalizada por precisar de um novo hash, incluindo hashes não compatíveis com criptografia.Por exemplo:
class FakeDB { public function __call($name, $args) { printf("%s::%s(%s)\n", __CLASS__, $name, json_encode($args)); return $this; } } class MyAuth { protected $dbh; protected $fakeUsers = [ // old crypt-md5 format 1 => ['password' => '$1$AVbfJOzY$oIHHCHlD76Aw1xmjfTpm5.'], // old salted md5 format 2 => ['password' => '3858f62230ac3c915f300c664312c63f', 'salt' => 'bar'], // current bcrypt format 3 => ['password' => '$2y$10$3eUn9Rnf04DR.aj8R3WbHuBO9EdoceH9uKf6vMiD7tz766rMNOyTO'] ]; public function __construct($dbh) { $this->dbh = $dbh; } protected function getuser($id) { // just pretend these are coming from the DB return $this->fakeUsers[$id]; } public function authUser($id, $password) { $userInfo = $this->getUser($id); // Do you have old, turbo-legacy, non-crypt hashes? if( strpos( $userInfo['password'], '$' ) !== 0 ) { printf("%s::legacy_hash\n", __METHOD__); $res = $userInfo['password'] === md5($password . $userInfo['salt']); } else { printf("%s::password_verify\n", __METHOD__); $res = password_verify($password, $userInfo['password']); } // once we've passed validation we can check if the hash needs updating. if( $res && password_needs_rehash($userInfo['password'], PASSWORD_DEFAULT) ) { printf("%s::rehash\n", __METHOD__); $stmt = $this->dbh->prepare('UPDATE users SET pass = ? WHERE user_id = ?'); $stmt->execute([password_hash($password, PASSWORD_DEFAULT), $id]); } return $res; } } $auth = new MyAuth(new FakeDB()); for( $i=1; $i<=3; $i++) { var_dump($auth->authuser($i, 'foo')); echo PHP_EOL; }
Resultado:
MyAuth::authUser::password_verify MyAuth::authUser::rehash FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"]) FakeDB::execute([["$2y$10$zNjPwqQX\/RxjHiwkeUEzwOpkucNw49yN4jjiRY70viZpAx5x69kv.",1]]) bool(true) MyAuth::authUser::legacy_hash MyAuth::authUser::rehash FakeDB::prepare(["UPDATE users SET pass = ? WHERE user_id = ?"]) FakeDB::execute([["$2y$10$VRTu4pgIkGUvilTDRTXYeOQSEYqe2GjsPoWvDUeYdV2x\/\/StjZYHu",2]]) bool(true) MyAuth::authUser::password_verify bool(true)
Como uma observação final, dado que você só pode refazer o hash de uma senha de usuário no login, você deve considerar "encerrar" hashes legados inseguros para proteger seus usuários. Com isso, quero dizer que, após um certo período de carência, você remove todos os hashes inseguros [por exemplo: MD5 / SHA / de outra forma fracos] e faz com que seus usuários confiem nos mecanismos de redefinição de senha do seu aplicativo.
fonte
Código completo da senha da classe:
Class Password { public function __construct() {} /** * Hash the password using the specified algorithm * * @param string $password The password to hash * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) * @param array $options The options for the algorithm to use * * @return string|false The hashed password, or false on error. */ function password_hash($password, $algo, array $options = array()) { if (!function_exists('crypt')) { trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); return null; } if (!is_string($password)) { trigger_error("password_hash(): Password must be a string", E_USER_WARNING); return null; } if (!is_int($algo)) { trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); return null; } switch ($algo) { case PASSWORD_BCRYPT : // Note that this is a C constant, but not exposed to PHP, so we don't define it here. $cost = 10; if (isset($options['cost'])) { $cost = $options['cost']; if ($cost < 4 || $cost > 31) { trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); return null; } } // The length of salt to generate $raw_salt_len = 16; // The length required in the final serialization $required_salt_len = 22; $hash_format = sprintf("$2y$%02d$", $cost); break; default : trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); return null; } if (isset($options['salt'])) { switch (gettype($options['salt'])) { case 'NULL' : case 'boolean' : case 'integer' : case 'double' : case 'string' : $salt = (string)$options['salt']; break; case 'object' : if (method_exists($options['salt'], '__tostring')) { $salt = (string)$options['salt']; break; } case 'array' : case 'resource' : default : trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); return null; } if (strlen($salt) < $required_salt_len) { trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING); return null; } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { $salt = str_replace('+', '.', base64_encode($salt)); } } else { $salt = str_replace('+', '.', base64_encode($this->generate_entropy($required_salt_len))); } $salt = substr($salt, 0, $required_salt_len); $hash = $hash_format . $salt; $ret = crypt($password, $hash); if (!is_string($ret) || strlen($ret) <= 13) { return false; } return $ret; } /** * Generates Entropy using the safest available method, falling back to less preferred methods depending on support * * @param int $bytes * * @return string Returns raw bytes */ function generate_entropy($bytes){ $buffer = ''; $buffer_valid = false; if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { $buffer = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM); if ($buffer) { $buffer_valid = true; } } if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { $buffer = openssl_random_pseudo_bytes($bytes); if ($buffer) { $buffer_valid = true; } } if (!$buffer_valid && is_readable('/dev/urandom')) { $f = fopen('/dev/urandom', 'r'); $read = strlen($buffer); while ($read < $bytes) { $buffer .= fread($f, $bytes - $read); $read = strlen($buffer); } fclose($f); if ($read >= $bytes) { $buffer_valid = true; } } if (!$buffer_valid || strlen($buffer) < $bytes) { $bl = strlen($buffer); for ($i = 0; $i < $bytes; $i++) { if ($i < $bl) { $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); } else { $buffer .= chr(mt_rand(0, 255)); } } } return $buffer; } /** * Get information about the password hash. Returns an array of the information * that was used to generate the password hash. * * array( * 'algo' => 1, * 'algoName' => 'bcrypt', * 'options' => array( * 'cost' => 10, * ), * ) * * @param string $hash The password hash to extract info from * * @return array The array of information about the hash. */ function password_get_info($hash) { $return = array('algo' => 0, 'algoName' => 'unknown', 'options' => array(), ); if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) { $return['algo'] = PASSWORD_BCRYPT; $return['algoName'] = 'bcrypt'; list($cost) = sscanf($hash, "$2y$%d$"); $return['options']['cost'] = $cost; } return $return; } /** * Determine if the password hash needs to be rehashed according to the options provided * * If the answer is true, after validating the password using password_verify, rehash it. * * @param string $hash The hash to test * @param int $algo The algorithm used for new password hashes * @param array $options The options array passed to password_hash * * @return boolean True if the password needs to be rehashed. */ function password_needs_rehash($hash, $algo, array $options = array()) { $info = password_get_info($hash); if ($info['algo'] != $algo) { return true; } switch ($algo) { case PASSWORD_BCRYPT : $cost = isset($options['cost']) ? $options['cost'] : 10; if ($cost != $info['options']['cost']) { return true; } break; } return false; } /** * Verify a password against a hash using a timing attack resistant approach * * @param string $password The password to verify * @param string $hash The hash to verify against * * @return boolean If the password matches the hash */ public function password_verify($password, $hash) { if (!function_exists('crypt')) { trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); return false; } $ret = crypt($password, $hash); if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) { return false; } $status = 0; for ($i = 0; $i < strlen($ret); $i++) { $status |= (ord($ret[$i]) ^ ord($hash[$i])); } return $status === 0; } }
fonte
Eu construí uma função que uso o tempo todo para validação de senha e para criar senhas, por exemplo, para armazená-las em um banco de dados MySQL. Ele usa um salt gerado aleatoriamente que é muito mais seguro do que usar um salt estático.
function secure_password($user_pwd, $multi) { /* secure_password ( string $user_pwd, boolean/string $multi ) *** Description: This function verifies a password against a (database-) stored password's hash or returns $hash for a given password if $multi is set to either true or false *** Examples: // To check a password against its hash if(secure_password($user_password, $row['user_password'])) { login_function(); } // To create a password-hash $my_password = 'uber_sEcUrE_pass'; $hash = secure_password($my_password, true); echo $hash; */ // Set options for encryption and build unique random hash $crypt_options = ['cost' => 11, 'salt' => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)]; $hash = password_hash($user_pwd, PASSWORD_BCRYPT, $crypt_options); // If $multi is not boolean check password and return validation state true/false if($multi!==true && $multi!==false) { if (password_verify($user_pwd, $table_pwd = $multi)) { return true; // valid password } else { return false; // invalid password } // If $multi is boolean return $hash } else return $hash; }
fonte
salt
parâmetro, ele será gerado automaticamente pela função password_hash () , seguindo as melhores práticas. Em vez dePASSWORD_BCRYPT
um, pode usarPASSWORD_DEFAULT
para escrever código à prova de futuro.