¿Que es un HASH?
Una función criptográfica hash- usualmente conocida como “hash”- es un algoritmo matemático de seguridad que transforma cualquier bloque arbitrario de datos en una nueva serie de caracteres, con la característica particular de siempre poseer una longitud fija. Esto quiere decir que, sin importar la longitud de los datos de entrada, la longitud del valor hash de salida siempre es la misma.
Para más información sobre el tema puedes visitar este blog de kaspersky
Gracias!
Script auxiliar generate_hash.php
<?php
$time =time();echo "Time: $time".PHP_EOL."Hash: ".sha1($argv[1].$time.'Sh!! No se lo cuentes a nadie!');
Le falto esta colita del código: ........'Sh!! No se lo cuentes a nadie!').PHP_EOL;
saludos!
Autenticación vía HMAC
Para esta autenticación necesitamos 3 elementos:
Función Hash: Difícil de romper, que sea conocida por el cliente y servidor
Clave secreta: Solamente la pueden saber el cliente y el servidor, será utilizada para corroborar el hash.
UID: El id del usuario, será utilizado dentro de la función hash junto con la clave secreta y un timestamp.
Es mucho más segura que la autenticación vía HTTP, por ello la información que se envía a través de este método no es muy sensible.
<?php
/*
-- Autenticacion via HTTP
$user = array_key_exists('PHP_AUTH_USER',$_SERVER) ? $_SERVER['PHP_AUTH_USER'] : '';
$pwd = array_key_exists('PHP_AUTH_PW',$_SERVER) ? $_SERVER['PHP_AUTH_PW'] : '';
if ($user !== 'mauro' || $pwd !== '1234') {
die;
}
*/// Autenticacion via HMACif(!array_key_exists('HTTP_X_HASH',$_SERVER)||!array_key_exists('HTTP_X_TIMESTAMP',$_SERVER)||!array_key_exists('HTTP_X_UID',$_SERVER)){ die;}list($hash,$uid,$timestamp)=[ $_SERVER['HTTP_X_HASH'], $_SERVER['HTTP_X_UID'], $_SERVER['HTTP_X_TIMESTAMP']];$secret ='Sh!! No se lo cuentes a nadie!';$newHash =sha1($uid.$timestamp.$secret);if($newHash !== $hash){ die;}// Definimos los recursos disponibles$allowedResourceTypes =['books','authors','genres',];// Validamos que el recurso este disponible$resourceType = $_GET['resource_type'];if(!in_array($resourceType,$allowedResourceTypes)){ die;}// Defino los recursos$books =[1=>['titulo'=>'Lo que el viento se llevo','id_autor'=>2,'id_genero'=>2,],2=>['titulo'=>'La Iliada','id_autor'=>1,'id_genero'=>1,],3=>['titulo'=>'La Odisea','id_autor'=>1,'id_genero'=>1,],];// Se indica al cliente que lo que recibirá es un jsonheader('Content-Type: application/json');// Levantamos el id del recurso buscado// utilizando un operador ternario$resourceId =array_key_exists('resource_id', $_GET)? $_GET['resource_id']:'';// Generamos la respuesta asumiendo que el pedido es correctoswitch(strtoupper($_SERVER['REQUEST_METHOD'])){case'GET':if(empty($resourceId)){ echo json_encode($books);}else{if(array_key_exists($resourceId,$books)){ echo json_encode($books[$resourceId]);}}break;case'POST': $json =file_get_contents('php://input'); $books[]=json_decode($json,true);//echo array_keys($books)[count($books)-1];end($books);// move the internal pointer to the end of the array $key =key($books);// fetches the key of the element pointed to by the internal pointer echo json_encode($books[$key]);break;case'PUT':// Validamos que el recurso buscado existaif(!empty($resourceId)&&array_key_exists($resourceId,$books)){// Tomamos la entrada curda $json =file_get_contents('php://input');// Tansformamos el json recibido a un nuevo elemento $books[$resourceId]=json_decode($json,true); echo json_encode($books[$resourceId]);}break;case'DELETE':// Validamos que el recurso buscado existaif(!empty($resourceId)&&array_key_exists($resourceId,$books)){unset($books[$resourceId]); echo json_encode($books);}break;}// Inicio el servidor en la terminal 1// php -S localhost:8000 server.php// Terminal 2 ejecutar // curl http://localhost:8000 -v// curl http://localhost:8000/\?resource_type\=books// curl http://localhost:8000/\?resource_type\=books | jq
Gracias
Apuntes:
Esta autenticación es un poco más segura.
Es un Código de autorización basado en hash de mensajes.
Un hash es una funcionalidad que encripta un texto. Se basa en un hash (encriptada) que se conoce por el cliente o servidor y luego un token de verificación conocido por el cliente y servidor (solamente) y el UID (User ID), que correspondería a la ID del usuario que será usada en la función hash junto con la contraseña secreta y un timestamp.
El procedimiento es parecido al anterior, a diferencia que se tomaran 3 variables globales de servidor (HTTP_X_HASH, HTTP_X_TIMESTAMP, HTTP_X_UID).
Version en python con flask
Cuando lo haces en otra tecnología con otro lenguaje en realidad aprendes como funciona porque no solo vez y sigues el código sino que piensas, armas la lógica de auhtificacion usando otras librerías o paquetes
Agregue un poco de comentarios :)
def auth_required(func): def wrapper(self):try: hash_api = request.headers.get('X-HASH') # Get the headers
hash_client = hmac.new(key=SECRET.encode(), digestmod=hashlib.sha1) # Set the hash algorithm
hash_client.update(request.headers.get('X-UID').encode()) # Set the UID hash_client.update(request.headers.get('X-TIMESTAMP').encode()) # Set the TIMESTAMP(UNIX) # If hashes are the same, then give access to the functionif hash_api == hash_client.hexdigest():returnfunc(self) # An error occured trying to resolve the values necesarry for the hash
except:returnmake_response('Please authenticate by HMAC on X headers!',401,{'WWW-Autencticate':'Basic reaml="Login Required"'}) # Hashes are not a mach,return an error
returnmake_response('Could not verify your login!',401,{'WWW-Autencticate':'Basic reaml="Login Required"'})return wrapper
++Mi intento en javascript++
Actualizando el middleware que hice en la clase anterior, para permitir HTTP o HMAC dependiendo cual se envíe
// Middleware de autenticaciónconstbookAuthenticator=(req, res, next)=>{// Obtiene la autenticación como "username:password" // Y lo convierte en un array como ["username", "password"]let authorization =[];// Verificamos si se envian headers de autenticación HTTPif(req.headers.authorization){// req.headers.authorization es encodeado en base64, tal como: "Basic bHVjbmVvbmN0OjEyMzQ="// Con un Buffer lo decodeamos: Buffer.from('string','base64') authorization =Buffer// Separamos en dos arreglos, para usar solo el string encodeado// ["Basic", "bHVjbmVvbmN0OjEyMzQ="].from(req.headers.authorization.split(" ")[1],'base64')// Lo transformamos en utf-8// "lucneonct:1234".toString('utf-8')// Lo separamos por el ":" en un array// ["lucneonct", "1234"].split(':');}// Verificamos si se envian headers de autenticación HMACelseif(req.headers['x-hash']){// Recibe los parametros enviados en req.headers// Si no hay se lo deja en blancoconst hash = req.headers['x-hash']||'';const uid = req.headers['x-uid']||'';const timestamp = req.headers['x-timestamp']||'';// Creamos nuestro secretconst secret ='Sh!! No se lo cuentes a nadie!';// Usamos el paquete de crypto que viene con NodeJS para crear el SHA1const hmac = crypto.createHmac('sha1', secret);// Pasamos nuestro UID y TIMESTAMP al método update hmac.update( uid + timestamp );// Comparamos que el hash enviado y el creado sean iguales// Usamos hmac.digest('hex') para que nos muestre el hash en hexadecimalif(hash === hmac.digest('hex')){// Rellenamos nuestro arreglo de autorización para pasar la validación posterior authorization =['Lucneonct','1234'];}}// Obtiene username del primer indice del arreglo y la contraseña del segundoconst authUser = authorization[0];const authPassword = authorization[1];// Verificamos si los datos no coinciden con nuestro usuario hardcodeadoif( authUser !=="Lucneonct"&& authPassword !=="1234"){// Rompe la cadena y retorna un 403 Forbiddenreturn res.status(403).json({status:'Forbidden: missing or invalid auth'});}next()}
Para hacer la solicitud, el hash lo generé en https://www.freeformatter.com/hmac-generator.html. usando id = 1+timestamp = 123456789 Quedaría 1123456789 y el secret Sh!! No se lo cuentes a nadie!
Usando curl con HMAC sería de la siguiente forma:
Vale, prácticamente HMAC lo que hace es mandar algunos datos encriptados con base en una "contraseña" y simplemente el servidor usa la misma contraseña para encriptar esos mismos datos y ver si da el mismo hash para entonces otorgar la autenticación
Autenticación vía HMAC
El método de autenticación HMAC o Hash-based message authentication code (Código de autenticación de mensajes basado en hash), y la idea de esta autenticación se basa en 3 conceptos principales.
Función de Hash, que sea difícil de romper y que sea conocida tanto por el cliente como el servidor.
Palabra secreta, también es compartida por el cliente y el servidor.
Datos que viajan de forma pública.
Cómo funciona
Lo que sucede es que el cliente sabiendo la función de hash y el secreto, toma esa información pública y con todo eso concatena y arma un mensaje que será enviado al servidor.
El servidor toma una información pública, el servidor ya conoce la información de hashing y el secreto, y vuelve a generar ese hash. Y luego, compara el recibido con el generado por el servidor, si coinciden la autenticación se produce, si no coinciden el servidor asume que ese usuario no está habilitado.
Para saber si ciertos encabezados estén en el array de encabezados que recibimos utilizamos HTTP_X_HASH, estos se suelen denotar con la letra X para decir que son encabezados no estándar.
!array_key_exists('HTTP_X_HASH')
list() → Nos deja asignar variables como si fueran un array, no es realmente una función, es un constructor del lenguaje. Este se utiliza para asignar una lista de variables en una sola operación.
Para crear un autenticación HMAC tenemos que:
Crear una condicional que si algunos de los encabezados no estándar que necesitamos no vienen datos por el cliente, muera el programa ahí.
Hace una lista de variables que contengan los encabezados no estándar que necesitamos.
Creamos una variable con nuestra palabra secreta
Y una variable que contenga el hash, dentro de la variable hacemos una función hash pasándole los parámetros del ID del usuario, el timestamp y la palabra secreta
Al final creamos otra condicional donde evaluamos si nuestro Hash no es igual al Hash que nos a dado el usuario. Si es verdad que no es igual, el programa termina en esa línea.
if(!array_key_exists('HTTP_X_HASH',$_SERVER)||!array_key_exists('HTTP_X_TIMESTAMP',$_SERVER)||!array_key_exists('HTTP_X_UID',$_SERVER)){die;}list($hash,$uid,$timestamp)=[$_SERVER['HTTP_X_HASH'],$_SERVER['HTTP_X_UID'],$_SERVER['HTTP_X_TIMESTAMP'],];$secret='Sh!! Es un secreto';$newHash=sha1($uid.$timestamp.$secret);if($newHash!==$hash){die;}
En nuestro caso para poder obtener un timestamp y un hash creamos otro archivo y escribimos:
<?php$time=time();echo"Time: $time".PHP_EOL."Hash: ".sha1($argv[1].$time.'Sh!! Es un secreto').PHP_EOL;
Y en la terminal, para pasar los datos escribimos:
La -H es para indicar header, aquí pasamos los datos extras para los headers
Lo que entiendo es que cuando el cliente se loguea por ejemplo el servidor le asigna el hash con el que va realizar las peticiones?, si es asi para que el cliente tenga a disposicion el has para enviarlo donde se almacena , en una cooky o en donde ?
La implementación por sha1() podría reemplazarse con el uso de hash_hmac() de la siguiente forma:
De manera general (a implementar tanto en El Cliente como en El Server):
$algo ='sha1';$key ='Sh!! No se lo cuentes a nadie!';
// ... considerar variables generales de arriba, y variable $timestamp del script server.php ...$data = $timestamp;$newHash_v2 =hash_hmac( $algo, $data, $key, $raw_output );
Hola necesito ayuda para entender algunas cosas.
1-. ¿En qué momento el cliente usa la palabra secreta? por lo que ví solo se usa en los archivos del servidor (server.php y generate_hash.php).
2-. Si el cliente no necesita saber la palabra secreta entonces ¿Cualquiera podría autenticarse solo conociendo el nombre del archivo generador de hash (generate_hash.php) y el ID de usuario?
Gracias :)
Hola, 1) para el código que realizó el profesor (y para todos los códigos que tengan alguna noción de seguridad) la palabra secreta sólo es conocida por el servidor, el cliente sólo puede recibir los datos ya procesados (hash, timestamp) y devolverlos así como los recibió; 2) en este caso, es cierto que cuálquiera que sepa el nombre del generador del archivo y tenga acceso a él podría obtener el hash, por eso en aplicaciones de producción se debe de controlar a qué tiene acceso el cliente, el profesor sólo ejemplificó el uso de los hashes para autenticación. PD: Existen muchas formas de estructurar correctamente un sistema de autenticación. Saludos
Gracias! entonces entiendo que puedo estructurar mi aplicación más allá del ejemplo de manera que la seguridad se consolide.
Recuerden que si están en windows la consola no lee comillas simples si no dobles en pocas palabras quedaría de la siguiente manera el comando en consola:
Sí están usando windows y la consola les marca el siguiente error:
curl:(6)Could not resolve host:
Prueben a usar comillas dobles en los parámetros
"X_HASH: blablablabla"
HMAC es un modo de autenticación de mensajes basado en hash. Ademas, es un mecanismo de autenticación de mensajes mediante funciones de hash cifradas.
HMAC basado en Hash de mensajes
No entendi porque el $SERVER la variable X-HASH aparece como HTTP_X_HASH con un guio bajo al final.
Hola! tengo una duda... la variable $argv[1] que utiliza el hash del documento generate_hash.php es UNICA? no se puede reemplazar?
Ya tiene un ano esta pregunta.
.
Por si acaso alguien mas la esta leyendo, en el ejercicio del profesor esta variable $argv[1] corresponde al ID del usario.
.
En la practica esta informacion proviene de una base de datos y es variable, claro que si. en la clase usamos el numero 1, por simplicidad del ejercicio, solamente para aprender el concepto de como autenticar usando HMAC.
No le veo mucho sentido al timestamp, no seria suficiente con el codigo secreto?