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
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;
}