Si los datos con los que está comparando no son secretos, entonces realmente no importa lo que haga. Los ataques de tiempo se utilizan para obtener información a la que aún no tiene acceso. Si ya tiene acceso, el ataque no es necesario.
Si está protegiendo un secreto, entonces no importa si es una contraseña o no. Debes manejarlo de la misma manera, con las mismas precauciones. Con el fin de evitar la sincronización de los canales laterales, se asegura de que la longitud de entrada sea la misma que la del valor almacenado, haciendo un hash entre ambos y haciendo una comparación de tiempo constante. Por supuesto, debe almacenar el valor de hash del valor almacenado en la base de datos, de modo que no lo vuelva a calcular cada vez. Vea el pseudocódigo a continuación.
Pseudocódigo para comparaciones constantes de tiempo:
bool compareToSecret(input/* input from user*/)
dbHash = getSecretFromDb()
inHash = hash(input); // sha256, bcrypt or whatever. Attacker
// knows how long this takes, but it
// doesn't get her anything
// at this point, both inhash and dbhash are the same length.
// the following loop is constant order time. Hashes are always the
// same length, and all values of the hashes are always compared
bool result = true
int len = strlen(inHash)
for( int i=0; i<len; i++ )
result &= (inHash[i] == dbHash[i])
return result
abortar temprano durante la comparación
Abortar temprano durante la comparación es problemático, ya que filtra información sobre la comparación y se puede usar para aprender la contraseña directamente.
- El atacante intenta secretos de longitud 1. Uno de estos secretos tardará unos milisegundos más en regresar que los otros, ya que hay un secreto de 1 longitud cuyo primer carácter coincide con el secreto almacenado. El atacante aprende el primer personaje del secreto.
- El atacante intenta 2 todos los secretos de carácter con la primera letra que comienza con un valor correcto conocido. Uno de estos secretos será unos milisegundos más rápido, ya que el segundo personaje se compara correctamente.
- El atacante se repite, atacando por separado a cada personaje del secreto. Finalmente, el atacante aprende el secreto almacenado.
Esto es más rápido que un ataque de fuerza bruta, porque el atacante puede atacar a cada personaje por separado en lugar de todo el secreto a la vez.
abortando temprano: antes de comparar
Abortar temprano, como lo menciona Steven Tousset, filtra la longitud del secreto. Esto no parece mucho al principio, pero le permite al atacante acelerar su ataque en órdenes de magnitud. Digamos que el sistema acepta secretos de longitud 1-5. Manteniéndolo muy simple, el secreto está compuesto solo de dígitos. Por lo tanto, hay 10 + 100 + 1,000 + 10,000 + 100,000 = 111,110 valores posibles. Un ataque de fuerza bruta necesita hacer aproximadamente 100,000 pruebas para probar todos los valores posibles.
Si el sistema le permite al atacante saber la longitud del secreto, un atacante puede reducir dramáticamente la cantidad de valores que necesita probar probando primero 5 valores: '1', '12', '123', '1234', y '12345'. Ahora el atacante sabe cuanto tiempo es el secreto. Si es 1 personaje, ahora solo tiene que hacer 10 pruebas en lugar de cien mil. Si tiene 2 caracteres, solo necesita hacer 100 pruebas. Sin este dato clave, el atacante necesita realizar muchas más pruebas antes de que encuentren el secreto.
otro problema: almacenamiento recuperable
También hay otro problema. Si el secreto conocido puede compararse con el valor de entrada, entonces tiene que almacenar el secreto en forma recuperable. Busque en este sitio los detalles relacionados con las contraseñas, pero esto a menudo se considera una mala práctica con el almacenamiento de contraseñas y debe evitarse. En general, con cualquier tipo de secreto, si no es absolutamente necesario tener el valor en bruto, no debe almacenarse en forma recuperable. El almacenamiento irrecuperable evita varios ataques, ya que su secreto depende de cómo lo use.
una cosa más: optimización del compilador
El código, a continuación, es un pseudo código. Si tuviera que pasar su equvalente a través de un compilador o un tiempo de ejecución que realiza la optimización, entonces debe tener mucho cuidado para asegurarse de que el código no se optimice. Hay varios trucos en diferentes idiomas, que están fuera del alcance de este comentario. Gracias a @Polynomial para recordarlo