Una cosa que no vi que se menciona aquí, aunque es una extensión de la respuesta de Marcus Adams, es que no debería usar una sola pieza de información para identificar y autenticar a un usuario si existe la posibilidad de ataques de tiempo , que pueden utilice las diferencias en los tiempos de respuesta para adivinar hasta dónde llegó una comparación de cadenas.
Si está utilizando un sistema que usa una "clave" para buscar el usuario o la credencial, esa información podría adivinarse gradualmente con el tiempo enviando miles de solicitudes y examinando el tiempo que le toma a su base de datos encontrar (o no buscar) un registro. Esto es especialmente cierto si la "clave" se almacena en texto plano en lugar de un hash unidireccional de la clave. Querrá almacenar las claves de los usuarios en un texto plano o cifradas simétricamente si necesita poder mostrar la clave al usuario nuevamente.
Al tener una segunda información, o "secreto", primero puede buscar el usuario o la credencial usando la "clave", que podría ser vulnerable a un ataque de sincronización, luego use una función de comparación segura de sincronización para verificar el valor de el secreto".
Aquí está la implementación de Python de esa función:
https://github.com/python/cpython/blob/cd8295ff758891f21084a6a5ad3403d35dda38f7/Modules/_operator.c#L727
Y está expuesto en la hmac
biblioteca (y probablemente en otros):
https://docs.python.org/3/library/hmac.html#hmac.compare_digest
Una cosa a tener en cuenta aquí es que no creo que este tipo de ataque funcione con valores hash o cifrados antes de la búsqueda, porque los valores que se comparan cambian aleatoriamente cada vez que cambia un carácter en la cadena de entrada. Encontré una buena explicación de esto aquí .
Las soluciones para almacenar claves API serían:
- Use una clave y un secreto separados, use la clave para buscar el registro y use una comparación segura de tiempo para verificar el secreto. Esto le permite volver a mostrarle al usuario la clave y el secreto.
- Use una clave y un secreto separados, use un cifrado simétrico y determinista en el secreto y haga una comparación normal de los secretos cifrados. Esto le permite mostrarle al usuario la clave y el secreto nuevamente, y podría evitar que tenga que implementar una comparación segura en el tiempo.
- Use una clave y un secreto separados, muestre el secreto, hash y almacénelo, luego haga una comparación normal del secreto hash. Esto elimina la necesidad de utilizar encriptación bidireccional y tiene el beneficio adicional de mantener su secreto seguro si el sistema se ve comprometido. Tiene la desventaja de que no puede volver a mostrar el secreto al usuario.
- Use una sola clave , muéstresela al usuario una vez, hash y luego haga una búsqueda normal de la clave cifrada o hash. Utiliza una única clave, pero no se puede volver a mostrar al usuario. Tiene la ventaja de mantener las claves seguras si el sistema se ve comprometido.
- Utilice una única clave , muéstresela al usuario una vez, cifrela y realice una búsqueda normal del secreto cifrado. Se puede volver a mostrar al usuario, pero a costa de que las claves sean vulnerables si el sistema se ve comprometido.
De estos, creo que 3 es el mejor equilibrio entre seguridad y conveniencia. He visto esto implementado en muchos sitios web cuando se emiten claves.
Además, invito a cualquier experto en seguridad real a criticar esta respuesta. Solo quería sacar esto como otro punto de discusión.