Uma coisa que eu não vi mencionado aqui, embora seja uma extensão da resposta de Marcus Adams, é que você não deve usar uma única informação para identificar e autenticar um usuário se houver a possibilidade de ataques temporais , que podem use as diferenças nos tempos de resposta para adivinhar o quão longe uma comparação de strings foi.
Se você estiver usando um sistema que usa uma "chave" para procurar o usuário ou credencial, essa informação pode ser adivinhada de forma incremental ao longo do tempo, enviando milhares de solicitações e examinando o tempo que leva para seu banco de dados encontrar (ou não encontrar) um registro. Isso é especialmente verdadeiro se a "chave" for armazenada em texto simples, em vez de um hash unilateral da chave. Você gostaria de armazenar as chaves dos usuários em um texto simples ou criptografado simetricamente para o caso de precisar ser capaz de exibir a chave para o usuário novamente.
Ao ter uma segunda informação, ou "segredo", você pode primeiro procurar o usuário ou credencial usando a "chave", que pode ser vulnerável a um ataque de temporização e, em seguida, usar uma função de comparação segura de temporização para verificar o valor de o segredo".
Aqui está a implementação dessa função em Python:
https://github.com/python/cpython/blob/cd8295ff758891f21084a6a5ad3403d35dda38f7/Modules/_operator.c#L727
E é exposto na hmac
biblioteca (e provavelmente em outras):
https://docs.python.org/3/library/hmac.html#hmac.compare_digest
Uma coisa a ser observada aqui é que não acho que esse tipo de ataque funcionará em valores que são hash ou criptografados antes da pesquisa, porque os valores que estão sendo comparados mudam aleatoriamente cada vez que um caractere na string de entrada muda. Eu encontrei uma boa explicação disso aqui .
As soluções para armazenar chaves de API seriam:
- Use uma chave e um segredo separados, use a chave para pesquisar o registro e use uma comparação segura de tempo para verificar o segredo. Isso permite que você mostre ao usuário a chave e o segredo novamente.
- Use uma chave e um segredo separados, use criptografia simétrica e determinística no segredo e faça uma comparação normal de segredos criptografados. Isso permite que você mostre ao usuário a chave e o segredo novamente, e pode evitar que você tenha que implementar uma comparação segura de tempo.
- Use uma chave e um segredo separados, exiba o segredo, faça um hash e armazene-o e, em seguida, faça uma comparação normal do segredo com hash. Isso elimina a necessidade de usar criptografia bidirecional e tem o benefício adicional de manter seu segredo seguro se o sistema for comprometido. A desvantagem é que você não pode mostrar o segredo ao usuário novamente.
- Use uma única chave , mostre-a ao usuário uma vez, faça um hash e faça uma pesquisa normal da chave criptografada ou com hash. Ele usa uma única chave, mas não pode ser mostrado ao usuário novamente. Tem a vantagem de manter as chaves seguras se o sistema for comprometido.
- Use uma única chave , mostre-a ao usuário uma vez, criptografe-a e faça uma pesquisa normal do segredo criptografado. Pode ser mostrado ao usuário novamente, mas ao custo de ter chaves vulneráveis se o sistema for comprometido.
Destes, acho que 3 é o melhor equilíbrio entre segurança e conveniência. Eu vi isso implementado em muitos sites ao obter as chaves emitidas.
Além disso, convido qualquer especialista em segurança real para criticar essa resposta. Eu só queria divulgar isso como outro ponto de discussão.