Ci sono dei difetti nella mia progettazione per una griglia OTP basata su carta?

1

Sto cercando di implementare l'autenticazione a due fattori a basso costo per un mio sito web. La mia banca utilizza questo tipo di sistema OTP basato su griglia, quindi ho voluto emularlo nella mia applicazione:

Lecartevengonogenerateutilizzandolestessefunzionidi passwordmaker.org . Una stringa pseudocasuale di 64 caratteri viene generata per ogni scheda e un numero ID di tessera univoco e questi vengono combinati insieme usando HMAC-RIPEMD-160 usando la stringa casuale come chiave e quindi una sorta di formula matematica viene applicata all'hash per produrre una lunga serie di caratteri dall'elenco di caratteri che fornisco (in questo caso, i numeri da 0 a 9). Ho quindi diviso questa lunga stringa per popolare i diversi valori della carta.

La stringa casuale e l'ID sono memorizzati in un database in testo semplice per convalidare un utente. In pratica tutto ciò che è necessario per riprodurre l'intera scheda è memorizzato nel DB.

C'è qualcosa di sbagliato in questo progetto finora?

C'è un modo per evitare di archiviare la stringa casuale nel database, ma essere ancora in grado di convalidare un utente dato che devono solo fornire i valori da 3 celle per accedere?

Come ho fatto io, i numeri prodotti non sono (necessariamente) unici, quindi non c'è nulla che possa impedire che il valore di una cella particolare si manifesti in qualsiasi altra cella. è una cosa buona o cattiva? I miei pensieri sono che è buono nel senso che se un utente malintenzionato dovesse imparare il valore di alcune delle celle sulla carta, questo non renderebbe più semplice determinare i valori delle altre celle.

Nel caso in cui qualcuno fosse interessato, ecco il codice PHP che crea la carta:

/**
 * 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;
}
    
posta Mike 09.08.2014 - 21:23
fonte

2 risposte

1

La principale preoccupazione che vorrei avere con il metodo proposto per generare i dati della griglia delle carte è se i numeri di griglia risultanti sono sufficientemente casuali. Dato che stai eseguendo dati generati casualmente attraverso un HMAC (che dovrebbe andare bene) ma anche una "formula matematica" è possibile che possa distorcere i numeri risultanti. Se un utente malintenzionato sa che alcuni numeri hanno una maggiore possibilità di presentarsi in una risposta di sfida, allora potrebbe aiutarli ad attaccare il sistema.

Altrimenti mi piace l'idea di non memorizzare la stringa casuale come record. Se un utente malintenzionato accede al database, non può prendere direttamente il record e ricreare la griglia di un utente. Dovrebbero anche avere accesso al tuo codice e ricreare il processo di conversione. Naturalmente, il tuo sistema non dovrebbe fare affidamento sul meccanismo di conversione che rimane segreto per la sua forza, ma in realtà ciò aiuta a resistere agli attacchi.

Non importa se certe sequenze numeriche appaiono più volte purché risultino da un buon generatore casuale, come notato sopra. Mentre qualcuno può occasionalmente farsi prendere dal panico se la loro risposta alla griglia è 00000 ("chiunque potrebbe indovinarlo!") Quella possibilità non dovrebbe essere più piacevole di qualsiasi altra.

    
risposta data 09.08.2014 - 23:14
fonte
1

Sembra che tu stia progettando molta complessità inutile in ... a meno che non mi sia sfuggito qualcosa.

Se hai 64 celle genera una sequenza di 64 caratteri di cifre casuali e memorizzale.

Stampa la tua carta da questo. E controlla le risposte alle sfide contro di esso. È davvero molto simile alla presenza di una password di 64 caratteri e alla richiesta di alcuni caratteri da essa per impedire ai keylogger ecc. Di ottenere l'intera password. Combinato con una password convenzionale e correttamente memorizzata, è in realtà abbastanza accurato.

In termini di sicurezza della sequenza se hai memorizzato correttamente una password convenzionale il compromesso non sarebbe un grosso problema in quanto non sarà riutilizzato da nessuna parte e non è in alcun modo personale. Tuttavia, per aggiungere protezione, l'app Web richiama una procedura memorizzata nel db per verificare le risposte di verifica e incrementare contatori dei tentativi, blocco ecc. Ecc piuttosto che consentire all'utente Web db di accedere alle tabelle delle password / segreti. Questo significa che ci vorrebbe un compromesso db completo per ottenere i dati anziché solo il server Web o un'iniezione SQL o simili.

    
risposta data 09.08.2014 - 21:50
fonte