¿Hay fallas en mi diseño para una cuadrícula OTP en papel?

1

Estoy tratando de implementar la autenticación de dos factores a bajo costo para un sitio web mío. Mi banco usa este tipo de sistema OTP basado en la red, por lo que quería emularlo en mi aplicación:

Lastarjetassegeneranusandolasmismasfuncionesque passwordmaker.org . Se genera una cadena pseudoaleatoria de 64 caracteres para cada tarjeta, así como un número único de ID de tarjeta, que se combinan usando HMAC-RIPEMD-160 utilizando la cadena aleatoria como clave y luego se aplica algún tipo de fórmula matemática al hash para producir una larga cadena de caracteres de la lista de caracteres que proporciono (en este caso, los números del 0 al 9). Luego divido esta larga cadena para rellenar los diferentes valores de la tarjeta.

La cadena y el ID aleatorios se almacenan en una base de datos en texto sin formato para validar a un usuario. Básicamente, todo lo que se necesita para reproducir la tarjeta completa se almacena en la base de datos.

¿Hay algún problema con este diseño hasta ahora?

¿Hay alguna manera de evitar el almacenamiento de la cadena aleatoria en la base de datos pero aún así puedo validar a un usuario dado que solo necesitan proporcionar los valores de 3 celdas para iniciar sesión?

Tal como lo tengo, los números producidos no son (necesariamente) únicos, por lo que no hay nada que impida que el valor de una celda en particular aparezca en cualquier otra celda. ¿Es esto algo bueno o malo? Mi opinión es que es bueno en el sentido de que si un atacante aprendiera el valor de algunas de las celdas de la tarjeta, esto no facilitaría la determinación de los valores de las otras celdas.

En caso de que alguien esté interesado, aquí está el código PHP que crea la tarjeta:

/**
 * Gets all cell values for the grid
 * @Return Array
 */
private function _getGridValues()
{
    if (!isset($this->_num_rows)) {
        throw new Exception("Number of rows is not set");
    }
        if (!isset($this->_num_cols)) {
        throw new Exception("Number of cols is not set");
    }
    $hash = $this->_getStringHash($this->_num_rows * $this->_num_cols * 2);

    $rows = str_split($hash, $this->_num_cols * 2);

    foreach ($rows as $index => $row) {
        $cols = str_split($row, 2);

        $this->_values[$index] = $cols;
    }
}

/**
 * Uses HMAC-RIPEMD-160 to create a hash using a key and a salt
 * @Return String
 */
private function _getStringHash($length)
{
    if (!isset($this->_key)) {
        throw new Exception("Key is not set");
    }
    if (!isset($this->_salt)) {
        throw new Exception("Salt is not set");
    }
    $string = '';
    $count = 0;

    while (strlen($string) < $length && $count < 1000) {
        $key = ($count++) ? $this->_key."\n".$count : $this->_key;

        $string .= $this->_rstr2any(hex2bin(hash_hmac('ripemd160',$this->_salt, $key)), $this->_chars);

        if (!$string) {
            throw new Exception("Unknown error");
        }
    }
    return substr($string, 0, $length);
}

/**
* Convert a raw string to an arbitrary string encoding
* @Copyright http://sourceforge.net/projects/passwordmaker/files/PHP%20Edition/
* @License GNU LGPL version 2.1
*/
private function _rstr2any($input, $chars) {
    $divisor = strlen($chars);
    $remainders = Array();

    /* Convert to an array of 16-bit big-endian values, forming the dividend */
    // pad this
    $dividend = array_pad(array(), ceil(strlen($input) / 2), 0);
    $inp = $input; // Because Miquel is a lazy twit and didn't want to do a search and replace
    for($i = 0; $i < count($dividend); $i++) {
        $dividend[$i] = (ord($inp{$i * 2}) << 8) | ord($inp{$i * 2 + 1});
    }

    $full_length = ceil((float)strlen($input) * 8
        / (log(strlen($chars)) / log(2)));
    /*
    * Repeatedly perform a long division. The binary array forms the dividend,
    * the length of the encoding is the divisor. Once computed, the quotient
    * forms the dividend for the next step. We stop when the dividend is zero.
    * All remainders are stored for later use.
    */
    while(count($dividend) > 0) {
        $quotient = Array();
        $x = 0;
        for($i = 0; $i < count($dividend); $i++) {
            $x = ($x << 16) + $dividend[$i];
            $q = floor($x / $divisor);
            $x -= $q * $divisor;
            if(count($quotient) > 0 || $q > 0)
                $quotient[count($quotient)] = $q;
        }
        $remainders[count($remainders)] = $x;
        //$remainders[$j] = $x;
        $dividend = $quotient;
    }

    /* Convert the remainders to the output string */
    $output = "";
    for($i = count($remainders) - 1; $i >= 0; $i--)
        $output .= $chars{(int)$remainders[$i]};

    return $output;
}
    
pregunta Mike 09.08.2014 - 21:23
fuente

2 respuestas

1

La principal preocupación que tendría con el método propuesto para generar datos de cuadrícula de tarjetas es si los números de cuadrícula resultantes son lo suficientemente aleatorios. Dado que está ejecutando datos generados aleatoriamente a través de un HMAC (que debería estar bien) pero también es una "fórmula matemática", es posible que pueda sesgar los números resultantes. Si un atacante sabe que ciertos números tienen más posibilidades de aparecer en una respuesta de desafío, podría ayudarles a atacar el sistema.

De lo contrario, me gusta la idea de no almacenar la cadena aleatoria como un registro. Si un atacante obtiene acceso a la base de datos, no podría tomar directamente el registro y volver a crear la cuadrícula de un usuario. También tendrían que obtener acceso a su código y volver a crear el proceso de conversión. Por supuesto, su sistema no debe confiar en que el mecanismo de conversión permanezca en secreto por su fuerza, pero en realidad eso ayuda a resistir los ataques.

No importa si ciertas secuencias numéricas aparecen varias veces mientras sean el resultado de un buen generador aleatorio, como se señaló anteriormente. Si bien alguien puede entrar en pánico de vez en cuando, si su respuesta de la cuadrícula es 00000 ("¡cualquiera puede adivinar eso!"), Esa posibilidad no debería ser más probable que cualquier otra.

    
respondido por el PwdRsch 09.08.2014 - 23:14
fuente
1

Parece que estás diseñando mucha complejidad innecesaria en ... a menos que me haya perdido algo.

Si tiene 64 celdas, genere una secuencia de 64 caracteres de dígitos criptográficos aleatorios y guárdela.

Imprime tu tarjeta de esto. Y verifica las respuestas a los desafíos contra él. Es realmente muy similar a tener una contraseña de 64 caracteres y pedir algunos caracteres para evitar que los registradores de claves, etc. obtengan la contraseña completa. Combinado con una contraseña convencional y correctamente almacenada, en realidad es bastante limpio.

En términos de asegurar la secuencia, si ha almacenado correctamente una contraseña convencional, el compromiso de esto no sería un gran problema, ya que no se reutilizará en ningún lugar y no es de ninguna manera personal. Para agregar protección, haga que su aplicación web llame a un procedimiento almacenado en su base de datos para verificar las respuestas de desafío e incrementar los contadores de intentos, el bloqueo, etc., en lugar de dar acceso al usuario de la base de datos web a las tablas de contraseñas / secretos. Esto significa que se necesitaría un compromiso completo de la base de datos para obtener los datos en lugar de solo el servidor web o una inyección SQL o similar.

    
respondido por el Andy Boura 09.08.2014 - 21:50
fuente

Lea otras preguntas en las etiquetas