Limpeza de senhas de usuários

98

Como devo escapar ou limpar as senhas fornecidas pelo usuário antes de hash e armazená-las em meu banco de dados?

Quando os desenvolvedores de PHP consideram hash nas senhas dos usuários para fins de segurança, eles geralmente tendem a pensar nessas senhas como fariam com quaisquer outros dados fornecidos pelo usuário. Este assunto surge frequentemente em questões de PHP relacionadas ao armazenamento de senhas; o desenvolvedor muitas vezes quer para limpar a senha usando funções como escape_string()(em várias iterações), htmlspecialchars(), addslashes()e outros antes de hash-lo e armazená-lo no banco de dados.

Jay Blanchard
fonte
1
Você pode usar a codificação base64
MSS
Não, @MSS, você não deveria, porque base64 é codificação , não criptografando ou hash . As senhas sempre devem ter hash .
Jay Blanchard
1
Quero dizer, antes do hash;)
MSS
Você não deve e não precisa fazer isso antes de hash. Isso fará com que você tenha que escrever código adicional desnecessário @MSS
Jay Blanchard

Respostas:

99

Você nunca deve escapar, cortar ou usar qualquer outro mecanismo de limpeza em senhas que fará hash com PHP password_hash()por uma série de razões, a maior delas porque a limpeza adicional da senha requer código adicional desnecessário.

Você vai argumentar (e você vê isso em todas as postagens onde os dados do usuário são aceitos para uso em seus sistemas) que devemos limpar todas as entradas do usuário e você estaria certo para todas as outras informações que estamos aceitando de nossos usuários. As senhas são diferentes. As senhas com hash não podem oferecer nenhuma ameaça de injeção de SQL porque a string é transformada em hash antes de ser armazenada no banco de dados.

O ato de hash de uma senha é o ato de tornar a senha segura para armazenar em seu banco de dados. A função hash não dá um significado especial a nenhum byte, portanto, nenhuma limpeza de sua entrada é necessária por razões de segurança

Se você seguir os mantras de permitir que os usuários usem as senhas / frases que desejam e não limitar as senhas , permitindo qualquer comprimento, qualquer número de espaços e qualquer hash de caracteres especiais tornará a senha / frase secreta segura, não importa o que esteja contido dentro a senha. A partir de agora, o hash mais comum (o padrão), PASSWORD_BCRYPTtransforma a senha em uma string de 60 caracteres contendo um sal aleatório junto com as informações de senha em hash e um custo (o custo algorítmico de criar o hash):

PASSWORD_BCRYPT é usado para criar novos hashes de senha usando o algoritmo CRYPT_BLOWFISH. Isso sempre resultará em um hash usando o formato de criptografia "$ 2y $", que sempre tem 60 caracteres de largura.

Os requisitos de espaço para armazenar o hash estão sujeitos a alterações à medida que diferentes métodos de hash são adicionados à função, portanto, é sempre melhor aumentar o tipo de coluna para o hash armazenado, como VARCHAR(255)ou TEXT.

Você poderia usar uma consulta SQL completa como sua senha e seria um hash, tornando-a inexecutável pelo mecanismo SQL, por exemplo,

SELECT * FROM `users`;

Pode ser hash para $2y$10$1tOKcWUWBW5gBka04tGMO.BH7gs/qjAHZsC5wyG0zmI2C.KgaqU5G

Vamos ver como diferentes métodos de sanitização afetam a senha -

A senha é I'm a "dessert topping" & a <floor wax>!(há 5 espaços no final da senha que não são exibidos aqui).

Quando aplicamos os seguintes métodos de corte, obtemos alguns resultados totalmente diferentes:

var_dump(trim($_POST['upassword']));
var_dump(htmlentities($_POST['upassword']));
var_dump(htmlspecialchars($_POST['upassword']));
var_dump(addslashes($_POST['upassword']));
var_dump(strip_tags($_POST['upassword']));

Resultados:

string(40) "I'm a "dessert topping" & a <floor wax>!" // spaces at the end are missing
string(65) "I'm a &quot;dessert topping&quot; &amp; a &lt;floor wax&gt;!     " // double quotes, ampersand and braces have been changed
string(65) "I'm a &quot;dessert topping&quot; &amp; a &lt;floor wax&gt;!     " // same here
string(48) "I\'m a \"dessert topping\" & a <floor wax>!     " // escape characters have been added
string(34) "I'm a "dessert topping" & a !     " // looks like we have something missing

O que acontece quando os enviamos para password_hash()? Todos eles são hash, assim como a consulta acima. O problema surge quando você tenta verificar a senha. Se empregarmos um ou mais desses métodos, devemos reutilizá-los antes de compará-los com password_verify(). O seguinte iria falhar:

password_verify($_POST['upassword'], $hashed_password); // where $hashed_password comes from a database query

Você teria que executar a senha postada por meio do método de limpeza escolhido antes de usar o resultado disso na verificação de senha. É um conjunto desnecessário de etapas e não tornará o hash melhor.


Usando uma versão do PHP inferior a 5.5? Você pode usar o password_hash() pacote de compatibilidade .

Você realmente não deve usar hashes de senha MD5 .

Jay Blanchard
fonte
13
Não. Se ele criou sua senha com espaços à direita, o que é permitido, ele deve usá-los no login @DanBracuk
Jay Blanchard
12
Como assim @DanBracuk? Se permitirmos que o usuário configure a senha que deseja, incluindo espaços iniciais / finais?
Jay Blanchard
16
É por isso que a maioria das coisas exige que você digite a senha escolhida duas vezes. Se o usuário adicionou os espaços por acidente, ele descobrirá antes de prosseguir. Se o usuário fez isso de propósito, então não é um problema.
Lutei com um urso uma vez.
4
@MargaretBloom, uma regra prática é apenas uma heurística. Às vezes, ainda precisamos refletir sobre as coisas, como por exemplo para senhas. Você diz "ninguém sabe como as coisas vão mudar no futuro", mas parece que se alguma coisa vai mudar é a maneira como escapamos dos dados antes de colocá-los no banco de dados, caso em que os usuários ficariam bloqueados quando suas senhas não mais corresponder ao que armazenamos. Qual é o perigo de não escapar dos hashes de senha versus o perigo de escapar deles?
DavidS
3
Exatamente: é claro que você "escapará do hash" no sentido limitado de passá-lo corretamente para uma consulta SQL parametrizada, onde algum código em seu conector SQL pode ou não fazer algo que corresponda a "escapar", você não t sei e não me importo. Você simplesmente não terá que escrever nenhum código específico para conseguir isso, porque é completamente rotineiro para todas as suas consultas SQL, a menos que você tenha feito anteriormente algumas decisões ruins.
Steve Jessop
36

Antes de fazer o hash da senha, você deve normalizá-la conforme descrito na seção 4 do RFC 7613 . Em particular:

  1. Regra de mapeamento adicional: Quaisquer instâncias de espaço não ASCII DEVEM ser mapeadas para espaço ASCII (U + 0020); um espaço não ASCII é qualquer ponto de código Unicode com uma categoria geral Unicode de "Zs" (com exceção de U + 0020).

e:

  1. Regra de normalização: O Formulário C de normalização Unicode (NFC) DEVE ser aplicado a todos os caracteres.

Isso tenta garantir que, se o usuário digitar a mesma senha, mas usando um método de entrada diferente, a senha ainda deverá ser aceita.

legoscia
fonte
3
@DavidS, um Mac Book norte-americano super brilhante (que Joe usou antes de sair) e um computador taiwanês mal internacionalizado (que Joe está tentando usar para fazer download é um cartão de embarque de voo de volta).
Margaret Bloom de
2
Parece jingoístico. :-) Obrigado.
DavidS
3
Hmm. Se você fizer isso, também deverá validar as senhas para rejeitar qualquer uma que contenha caracteres ainda não atribuídos. Seria terrível se um usuário usasse NEWFANGLED SPACE, que seu aplicativo não reconhece e, portanto, faz o hash no estado em que se encontra, e então você atualiza seu banco de dados de caracteres Unicode e, de repente, NEWFANGLED SPACE é mapeado para SPACE antes do hashing, de forma que ele não pode mais inserir uma senha que seu aplicativo fará com o hash antigo.
ruakh
4
@JayBlanchard Porque quando você pressiona uma barra de espaço em uma máquina e quando você pressiona em outra máquina, você pode obter dois pontos de código Unicode diferentes, e eles terão duas codificações UTF-8 diferentes, sem que o usuário saiba de nada. Pode-se argumentar que esse é um problema que você deseja ignorar, mas o RFC 7613 nasceu de tais questões da vida real, não é uma recomendação improvisada.
Reintegrar Monica em
1
@ruakh Uma vez que você decida como lidar com as senhas de uma certa maneira, elas devem permanecer assim, ou então as coisas serão interrompidas para os casos de uso existentes. Se você pretende alterar o método de pré-processamento no futuro, deve armazená-lo junto com a representação pré-processada e com hash da senha. Dessa forma, depois de receber a entrada, você seleciona o método de pré-processamento / hashing com base no que está comparando.
Reintegrar Monica em