Después de mucho batallar hemos dado con el resultado de esta obstáculo que agunos usuarios de nuestro sitio web han presentado. Si deseas compartir algún dato no dejes de compartir tu conocimiento.
Solución:
En realidad, es bastante fácil de hacer … Hay algunas bibliotecas para hacerlo por usted:
- Biblioteca PHP Nonce
- Biblioteca OpenID Nonce
O si quieres escribir el tuyo propio, es bastante simple. Usando la página de WikiPedia como punto de partida, en pseudocódigo:
En el lado del servidor, necesita dos funciones llamables de cliente
getNonce()
$id = Identify Request //(either by username, session, or something)
$nonce = hash('sha512', makeRandomString());
storeNonce($id, $nonce);
return $nonce to client;
verifyNonce($data, $cnonce, $hash)
$id = Identify Request
$nonce = getNonce($id); // Fetch the nonce from the last request
removeNonce($id, $nonce); //Remove the nonce from being used again!
$testHash = hash('sha512',$nonce . $cnonce . $data);
return $testHash == $hash;
Y del lado del cliente:
sendData($data)
$nonce = getNonceFromServer();
$cnonce = hash('sha512', makeRandomString());
$hash = hash('sha512', $nonce . $cnonce . $data);
$args = array('data' => $data, 'cnonce' => $cnonce, 'hash' => $hash);
sendDataToClient($args);
La función makeRandomString
realmente solo necesita devolver un número aleatorio o string. Cuanto mejor sea la aleatoriedad, mejor será la seguridad … También tenga en cuenta que, dado que se introduce directamente en una función hash, los detalles de implementación no importan de una solicitud a otra. No es necesario que la versión del cliente y la versión del servidor coincidan. De hecho, el único bit que debe coincidir al 100% es la función hash utilizada en hash('sha512', $nonce . $cnonce . $data);
… Aquí hay un ejemplo de un sistema razonablemente seguro makeRandomString
función…
function makeRandomString($bits = 256)
$bytes = ceil($bits / 8);
$return = '';
for ($i = 0; $i < $bytes; $i++)
$return .= chr(mt_rand(0, 255));
return $return;
Los nonces son una lata de gusanos.
No, realmente, una de las motivaciones para varias entradas CAESAR fue diseñar un esquema de cifrado autenticado, preferiblemente basado en un cifrado de flujo, que sea resistente a la reutilización nonce. (Reutilizar un nonce con AES-CTR, por ejemplo, destruye la confidencialidad de su mensaje al grado que un estudiante de programación de primer año podría descifrarlo).
Hay tres escuelas de pensamiento principales con nonces:
- En simétricokey criptografía: utilice un contador creciente, teniendo cuidado de no reutilizarlo nunca. (Esto también significa usar un contador separado para el remitente y el receptor). Esto requiere una programación con estado (es decir, almacenar el nonce en algún lugar para que cada solicitud no comience en
1
). - Nonces aleatorios con estado. Generar un nonce aleatorio y luego recordarlo para validarlo más tarde. Esta es la estrategia utilizada para derrotar los ataques CSRF, que suena más cercano a lo que se pide aquí.
- Grandes nonces aleatorios sin estado. Con un generador de números aleatorios seguro, casi puede garantizar que nunca repetirá un nonce dos veces en su vida. Esta es la estrategia utilizada por NaCl para el cifrado.
Entonces, con eso en mente, las principales preguntas que debe hacerse son:
- ¿Cuáles de las escuelas de pensamiento anteriores son más relevantes para el problema que está tratando de resolver?
- ¿Cómo estás generando el nonce?
- ¿Cómo estás validando el nonce?
Generando un Nonce
La respuesta a la pregunta 2 para cualquier nonce aleatorio es utilizar un CSPRNG. Para proyectos PHP, esto significa uno de:
random_bytes()
para proyectos PHP 7+- paragonie / random_compat, un polyfill PHP 5 para
random_bytes()
- ircmaxell / RandomLib, que es una navaja suiza de utilidades de aleatoriedad que la mayoría de los proyectos que se ocupan de la aleatoriedad (por ejemplo, restablecimiento de contraseñas) deberían considerar usar en lugar de utilizar las suyas propias.
Estos dos son moralmente equivalentes:
$factory = new RandomLibFactory;
$generator = $factory->getMediumStrengthGenerator();
$_SESSION['nonce'] [] = $generator->generate(32);
y
$_SESSION['nonce'] []= random_bytes(32);
Validando un Nonce
Con estado
Los nonces con estado son fáciles y recomendados:
$found = array_search($nonce, $_SESSION['nonces']);
if (!$found)
throw new Exception("Nonce not found! Handle this or the app crashes");
// Yay, now delete it.
unset($_SESSION['nonce'][$found]);
No dude en sustituir el array_search()
con una base de datos o una búsqueda en memcached, etc.
Apátridas (aquí hay dragones)
Este es un problema difícil de resolver: necesita alguna forma de evitar los ataques de repetición, pero su servidor tiene amnesia total después de cada solicitud HTTP.
La única solución sensata sería autenticar una fecha / hora de vencimiento para minimizar la utilidad de los ataques de repetición. Por ejemplo:
// Generating a message bearing a nonce
$nonce = random_bytes(32);
$expires = new DateTime('now')
->add(new DateInterval('PT01H'));
$message = json_encode([
'nonce' => base64_encode($nonce),
'expires' => $expires->format('Y-m-dTH:i:s')
]);
$publishThis = base64_encode(
hash_hmac('sha256', $message, $authenticationKey, true) . $message
);
// Validating a message and retrieving the nonce
$decoded = base64_decode($input);
if ($decoded === false)
throw new Exception("Encoding error");
$mac = mb_substr($decoded, 0, 32, '8bit'); // stored
$message = mb_substr($decoded, 32, null, '8bit');
$calc = hash_hmac('sha256', $message, $authenticationKey, true); // calcuated
if (!hash_equals($calc, $mac))
throw new Exception("Invalid MAC");
$message = json_decode($message);
$currTime = new DateTime('NOW');
$expireTime = new DateTime($message->expires);
if ($currTime > $expireTime)
throw new Exception("Expired token");
$nonce = $message->nonce; // Valid (for one hour)
Un observador atento notará que se trata básicamente de una variante de JSON Web Tokens que no cumple con los estándares.
Una opción (que mencioné en el comentario) es grabar el juego y reproducirlo en un entorno seguro.
La otra cosa es registrar aleatoriamente, o en algunos momentos específicos, algunos datos aparentemente inocentes, que luego se pueden usar para validarlos en el servidor (como de repente en vivo pasa del 1% al 100%, o la puntuación del 1 al 1000, lo que indica trampa ). Con suficientes datos, puede que no sea factible que un tramposo intente fingirlo. Y luego, por supuesto, implementar una fuerte prohibición :).