No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Autenticación vía HMAC

12/19
Recursos

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.

Aportes 44

Preguntas 10

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

¿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

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!');

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 HMAC
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!! 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 json
header('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 correcto
switch (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 exista
		if (!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 exista
		if (!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

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).

curl http://localhost:80/books -H “X-HASH: hashGenerado” -H “X-UID: tuIdUsuario” -H “X-TIMESTAMP: hashGeneradoDelTimeStamp”

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

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ón
const bookAuthenticator = (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 HTTP
  if(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 HMAC
  else if(req.headers['x-hash']) {
    // Recibe los parametros enviados en req.headers
    // Si no hay se lo deja en blanco
    const hash = req.headers['x-hash'] || '';
    const uid = req.headers['x-uid'] || '';
    const timestamp = req.headers['x-timestamp'] || '';

    // Creamos nuestro secret
    const secret = 'Sh!! No se lo cuentes a nadie!';

    // Usamos el paquete de crypto que viene con NodeJS para crear el SHA1
    const 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 hexadecimal
    if(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 segundo
  const authUser = authorization[0];
  const authPassword = authorization[1];

  // Verificamos si los datos no coinciden con nuestro usuario hardcodeado
  if( authUser !== "Lucneonct" && authPassword !== "1234") {
    // Rompe la cadena y retorna un 403 Forbidden
    return 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:

curl localhost:5000/?resource_type=books -H "X-HASH: 82db2fbfb7f3c476dca3d750d43810d22d38e8ea" -H "X-UID: 1" -H "X-TIMESTAMP: 123456789" | jq


Y si lo pongo de forma incorrecta, por ejemplo, restando 1 al timestamp:

curl localhost:5000/?resource_type=books -H "X-HASH: 82db2fbfb7f3c476dca3d750d43810d22d38e8ea" -H "X-UID: 1" -H "X-TIMESTAMP: 123456788" | jq


Y también se podría usar curl con HTTP

curl lucneonct:1234@localhost:5000/?resource_type=books | jq

Me funciono con comillas dobles en windows:

curl http://localhost:8000/books -H “X-HASH: ************” -H “X-UID: 1” -H “X-TIMESTAMP: *******”

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

<h3>Autenticación vía HMAC</h3>

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.

  1. Función de Hash, que sea difícil de romper y que sea conocida tanto por el cliente como el servidor.
  2. Palabra secreta, también es compartida por el cliente y el servidor.
  3. Datos que viajan de forma pública.
<h4>Cómo funciona</h4>

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:

  1. Crear una condicional que si algunos de los encabezados no estándar que necesitamos no vienen datos por el cliente, muera el programa ahí.
  2. Hace una lista de variables que contengan los encabezados no estándar que necesitamos.
  3. Creamos una variable con nuestra palabra secreta
  4. 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
  5. 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:

curl http :// localhost:8000 /books -H 'X-HASH: 711b6f2914bb9e5b5ebef25d488b930f71bc5977' -H 'X-UID: 1' -H 'X-TIMESTAMP: 1609857500'

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!';

Del lado de El Cliente:

// ... considerar variables generales de arriba
$data = time();
$raw_output = false;

$hash_v2 = hash_hmac( $algo, $data, $key, $raw_output );

echo "Time: $data" . PHP_EOL . "Hash: " . $hash_v2 . PHP_EOL;

Del lado de El Server:

// ... considerar variables generales de arriba, y variable $timestamp del script server.php ...
$data = $timestamp;

$newHash_v2 = hash_hmac( $algo, $data, $key, $raw_output );

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:

curl http://localhost:8000/books -H "X-HASH:tuhas" -H "X-UID: tuid" -H "X-TIMESTAMP: tiempo"

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.

Me funciono con comillas dobles, tenia duda pq en el archivo generate_hash.php tiene un $argv[1] pero parece viene definido así.

https://www.php.net/manual/es/reserved.variables.server.php

Me parece que hacerlo asi seria casi igual que solo tener un string encriptado en cada lado, lo único es que lo actualizaria cada x tiempo.

Interesante como conocimiento pero no lo usaria para la vida real.

Por lo que veo esto no aporta ninguna seguridad extra, es una falsa sensación de seguridad, ya que si alguien intercepta el encabezado, puede usarlo para autenticarse, sin necesidad de saber la contraseña. Con el hash, id y timestamp es suficiente para suplantar la identidad. Para que esto sea seguro, habría que reemplazar el timestamp por un dato aleatorio que solo el servidor conozca y que sea de un solo uso. De esta forma el hash sería diferente cada vez, y por mas que alguien lo intercepte, sería inutil

Para los que el hash generado es diferente al esperado, hay que cambiar algo en el archivo generate_hash.php:
Simplemente el 1 por un 0 en $argv

$time = time();
echo "Time: $time".PHP_EOL."Hash: ".sha1($argv[0].$time.'Sh!! No se lo cuentes a nadie!').PHP_EOL;

HMAC es mas seguro que el protocolo HTTP porque envia encriptado el mensaje con una funcion criptografica llamada HASH.

esta authenticacion si la conocia , pero nunca la habia probado pues sigue siendo insegura, vamos con token y no basic sino bearer o algo por el estilo

En Go:

func hmacAuth(next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hash := r.Header.Get("X_HASH")
		ts := r.Header.Get("X_TIMESTAMP")
		uid := r.Header.Get("X_UID")

		if hash == "" || ts == "" || uid == "" {
			w.WriteHeader(http.StatusUnauthorized)
			fmt.Fprint(w, "<h1>Unauthorized</h1>")
			return
		}

		secret := []byte("secret")

		newHash := hmac.New(sha256.New, secret)

		n, err := newHash.Write([]byte(ts + uid))
		if err != nil || n != len([]byte(ts+uid)) {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, "<h1>Internal Server Error</h1>")
			return
		}

		expectedHash := fmt.Sprintf("%x", newHash.Sum(nil))

		if hash != expectedHash {
			w.WriteHeader(http.StatusUnauthorized)
			fmt.Fprint(w, "<h1>Authentication error</h1>", hash, "-", newHash.Sum(nil))
			return
		}

		next.ServeHTTP(w, r)
	})

}

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.

Creamos la autentificación en el archivo server.php

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_UID'],
  $_SERVER['HTTP_TIMESTAMP'],
];
$secret = 'Sh!! No se lo cuentes a nadie!';
$newHash = sha1($uid,$timestamp,$secret);
if ($newHash  !== $hash) {
  die;
};

Luego en un nuevo archivo colocamos el codigo para crear un nuevo hash en generate-hash.php

<?php
$time = time();
echo "Time: $time".PHP_EOL."Hash: ".sha1($argv[1].$time.'Sh!! No se lo cuentes a nadie!').PHP_EOL;

Pedimos el hash a la consola

Php generate-hash.php 1 -> ese 1 es el nombre de usuario

Nos regresa algo como esto

Time: 1614193933
Hash: fa5d2569d539bea35cf387b30dc863ef20ad8a14

Luego usa esos números para ingresar este comando en la consola.

curl http ://localhost:8000/books -H 'X-HASH: 4b6609e5c46572dedb66920c31932d3cd0ca9f37' -H 'X-UID: 1' -H 'X-TIMESTAMP: 1614195581' | jq

Si lo hiciste bien, te devolvio el array de books.

Excelente

El timestamp es en segundos?

No estaria entendiendo bien.
Para que una persona pueda consumir mi Api utilizando este metodo de autentificacion, tengo que pasarle el Hash y el Time???

login con HMAC
http://dominio.com/metodo/ -H ‘X-HASH: tu_hash’ -H ‘X-UID: tu_usuario’ -H’X-TIMESTAMP: tu_marcaDeTiempo’

Pero no es el metodo mas seguro.

Autenticación vía HMAC
Más segura que la autenticación HTTP.
Necesita la Función Hash, la clave secreta y el UID.
$ curl http://localhost:8000/books -H 'X-HASH: ***' -H 'X-UID: 1' -H 'X-TIMESTAMP: ***'

Dependiendo de la sensibilidad de la data que se quiera exponer en un recurso, este método de utilizarse para consumir ese información. Excelente esta clase

hash message authentication code.

genial 😃 Hash

Cualquier función hash criptográfica, como SHA-2 o SHA-3 , puede usarse en el cálculo de un HMAC; el algoritmo MAC resultante se denomina HMAC-X, donde X es la función hash utilizada (por ejemplo, HMAC-SHA256 o HMAC-SHA3-256). La fuerza criptográfica del HMAC depende de la fuerza criptográfica de la función hash subyacente, el tamaño de su salida hash y el tamaño y la calidad de la clave.

Creo que sería bueno actualizar el curso, están utilizando como método de encriptación sha1(). Si bien hace unos años esta función era robusta, hoy en día no es recomendada porque ya las máquinas actuales pueden romper el hash en la mayoría de los casos, algo similar a lo que pasó con md5().

Vine aprender a utilizar una API Rest para la comunicación FrontEnd/BackEnd y terminé complicandome con estas cosas.

Tengo exactamente el mismo codigo que Mauro y se me generan dos hash distintos a la hora de hacer curl, no se si influya algo que este usando WSL en vez de powershell o ubuntu directamente

Explicacion HMAC

HMAC
Hash Message Authentication Code

  • Is a specific type of message authentication code (MAC) involving a cryptographic hash function(hence the ‘H’) in combination with a secret cryptographic key.
    Componentes
  • Hash
  • Secret Key
  • Public data

A la consola de Windows no le gustan las comillas simples xd.
Si alguien tiene problemas al hacer la autenticación con el servidor hay que cambiar las comillas simples por dobles
"X-HASH: ... "

Sugerencia, en vez de SHA-1 utilicen SHA256 o mejor aun (y si el tiempo/poder_de_procesamiento lo permite) SHA512

HMAC hash-based message authentication code o Código de autenticación de mensajes basado en hash

ya probe con comillas simples y dobles y no me funciono

Estoy aprendiendo algunas cosas de PHP al mismo tiempo de API REST que era uno de mis mayores temores pensando que era súper difícil el profesor explica excelente y más con el lenguaje que más manejó.

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 😃