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