Comparación de cadenas segura en el tiempo en lenguajes de alto nivel

1

Estoy buscando una solución confiable para comparar dos cadenas sin perder su contenido a través de las diferencias de tiempo. La longitud de las cadenas es no secreta.

El fondo es el siguiente: estoy implementando autenticación basada en contraseña para una aplicación web. Las contraseñas se procesan con bcrypt y se almacenan en una base de datos. En lugar de dar a la aplicación acceso directo a los hashes para que compruebe la contraseña, me gustaría delegar esto en el sistema de base de datos. La aplicación solo utiliza la contraseña y luego pasa el hash a un procedimiento de base de datos. Este procedimiento tiene privilegios especiales para acceder a los hash almacenados y compararlos con el hash proporcionado. El objetivo es proteger los hashes contra los ataques de inyección de SQL. Una idea similar se describe en esta presentación .

Obviamente, este esquema solo es efectivo si el procedimiento no filtra información sobre los hashes almacenados. Por lo tanto, la comparación de cadenas debe ser segura en el tiempo.

Soy consciente del siguiente método (pseudocódigo):

string_comp(str_1, str_2):
    if str_1.length != str_2.length:
        return false
    else:
        result := 0
        for i := 0 to str_1.length - 1:
            result := result | (str_1[i] ^ str_2[i])

        return result == 0

Sin embargo, esto es bastante engorroso, y no estoy seguro de si funciona como se espera en lenguajes de alto nivel como SQL.

Otra sugerencia común es hash las cadenas y luego comparar las hashes . Esto sería mucho más simple que el código anterior.

¿Qué método es preferible? Si utilizara la solución hash, ¿qué algoritmo elegiría para no degradar la fuerza de bcrypt (ni siquiera en teoría)? SHA-256? SHA-384? SHA-512?

    
pregunta Fleche 11.06.2014 - 00:34
fuente

2 respuestas

2

Tenga en cuenta que no puede calcular el hash bcrypt en la aplicación y pasarlo a la base de datos a menos que almacene las sales por separado de los hashes. Si lo hace, incurrirá en la sobrecarga de realizar primero un SELECT, luego el bcrypt y luego el paso de verificación de la contraseña. Hacer lo que sugiere la presentación (hacer que el DB calcule el hash) podría ser lo más sensato. El módulo crypt de Postgres 9 soporta bcrypt como un tipo de hash incorporado.

Si quiere hacer el hash en la aplicación de todos modos, comparar con un hash es probablemente el más simple (es probable que tenga problemas de implementación). Dado que bcrypt se basa en pez globo, solo considera hasta 448 bits de entrada, por lo que puede hacer un hash seguro en la salida con SHA-512 y matemáticamente no perderá fuerza. En realidad, SHA-256 también está bien, ya que las contraseñas de los usuarios esencialmente nunca contienen más de 256 bits de entropía. Incluso 256 bits de caracteres imprimibles aleatoriamente tomarían contraseñas de 56 caracteres de longitud. Es probable que comparar el uso de un hash tenga la implementación más sencilla, y como ya tiene la sobrecarga de bcrypt, el costo de 2 hashes adicionales es trivial.

(Esta parte de la respuesta se basa en una interpretación errónea de la pregunta porque le preocupa el momento de los ataques contra la aplicación, no la base de datos, pero la dejo aquí como referencia).

Si está utilizando bcrypt, ya ha ingresado la contraseña, por lo que no debe preocuparse por los resultados del tiempo. Incluso si alguien pudiera deducir el hash de la contraseña, aún necesitaría encontrar un texto simple para proporcionar a la aplicación, que debería ser computacionalmente inviable (o bcrypt está roto).

Los ataques de tiempo solo son útiles si el atacante puede recorrer los valores que se comparan para encontrar una comparación útil. (es decir, A, B, C ...) Con un valor de hash, esto no es posible, ya que no pueden iterar sobre la salida del hash (para hashes criptográficamente fuertes). Pequeños cambios en la entrada a bcrypt dan como resultado grandes cambios en la salida. Un usuario que proporciona la entrada a bcrypt no tiene forma de probar los prefijos y luego extender el prefijo. Un atacante no sabría nada sobre ninguno de los dos lados de la comparación y, por lo tanto, los ataques de tiempo no filtran ninguna información sobre el valor del hash.

    
respondido por el David 11.06.2014 - 00:51
fuente
1
  

En lugar de dar a la aplicación acceso directo a los hashes para que compruebe la contraseña, me gustaría delegar esto en el sistema de base de datos en sí.

¿Por qué quieres delegarlo? Es más sencillo simplemente obtener el hash para el nombre de usuario dado y hacer una comparación de cadenas de lado constante (usando el pseudo-código anterior) con el hash recuperado.

Cada inicio de sesión exitoso dará a la aplicación web que se ejecuta en su servidor tanto la contraseña ingresada como el hash computado de la contraseña.

Concedido lo que desea es bastante sencillo de hacer dentro de una base de datos determinada utilizando su lenguaje de procedimiento (PL). Por ejemplo, en postgres podrías escribir:

CREATE OR REPLACE FUNCTION user_with_correct_hash(username TEXT, app_hash TEXT) RETURNS BOOLEAN AS
$$
DECLARE
    user_hash TEXT;
BEGIN
    user_hash := SELECT hash FROM users WHERE "name" = username;
    IF (user_hash IS NULL) THEN
        RETURN false;
    END IF;
    RETURN constant_time_string_compare(user_hash, app_hash);
END
$$
LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION constant_time_string_compare(user_hash TEXT, app_hash TEXT) RETURNS BOOLEAN AS
$$
DECLARE
    result INTEGER;
    index INTEGER;
    hash_length INTEGER;
BEGIN 
    hash_length := CHAR_LENGTH(app_hash);
    IF CHAR_LENGTH(user_hash) != hash_length THEN
        RETURN false;
    END IF;
    index := 1;
    result := 0;
    WHILE index <= hash_length LOOP
        result := result | (ascii(substr(app_hash, index, 1)) # ascii(substr(user_hash, index, 1)));
        index := index + 1;
    END LOOP;
    RETURN (result = 0);
END
$$
LANGUAGE plpgsql;

Por supuesto, ten mucho cuidado de comparar varios casos de prueba en tu cadena de tiempo constante para asegurarte de que se está comportando correctamente.

    
respondido por el dr jimbob 11.06.2014 - 01:16
fuente

Lea otras preguntas en las etiquetas