En esta entrada vamos a ver qué es y cómo usar SHA-256, cómo funciona y cómo utilizarlo en un ESP32.
SHA-256 es un algoritmo de hashing considerado seguro que nos será muy útil a la hora de hacer tarea cómo:
- Autenticación: Validar la identidad de los dispositivos o usuarios.
- Almacenamiento seguro: Proteger contraseñas y claves en dispositivos embebidos.
- Verificar la integridad de los datos: Asegurar que los datos no han sido alterados durante la transmisión.
Pero antes vamos a ver brevemente que es el SHA-256 👇.
Qué es SHA-256
SHA-256 es un miembro de la familia de funciones hash SHA-2 (Secure Hash Algorithm 2) desarrolladas por el Instituto Nacional de Estándares y Tecnología (NIST).
Esta función hash toma una entrada de datos de cualquier longitud y genera un valor de 256 bits (32 bytes) que se representa como un valor hexadecimal de 64 dígitos
Características principales de SHA-256
- Tamaño de salida: 256 bits (32 bytes).
- Resistencia a colisiones: Es computacionalmente inviable encontrar dos entradas diferentes que produzcan el mismo hash.
- Determinista: La misma entrada siempre producirá el mismo hash.
- Irreversible: No es posible reconstruir la entrada original a partir del hash.
El hash SHA-256 es una representación única y fija de los datos originales, lo que significa que cualquier cambio en los datos generará un hash completamente diferente.
SHA-256 es un algoritmo unidireccional, lo que significa que no es posible obtener los datos originales a partir del hash.
Esto lo convierte en una herramienta muy útil para verificar la integridad de los datos y protegernos contra modificaciones no autorizadas.
Implementación de SHA-256 en el ESP32
El ESP32 cuenta con un módulo de hardware de cifrado que incluye soporte para SHA-256. Esto permite realizar operaciones de hash de manera eficiente sin sobrecargar la CPU.
Vamos a ver cómo implementar SHA-256 en el ESP32 utilizando la biblioteca mbedTLS
.
#include "mbedtls/md.h"
#include "stdlib.h"
void printBlockAsHex(std::vector<uint8_t> data)
{
for(int i = 0; i < data.size(); i++)
{
Serial.printf("%02X ", data[i]);
}
}
std::vector<uint8_t> generateSHA256(std::string& text)
{
std::vector<uint8_t> hash(32);
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, (const unsigned char*)text.c_str(), text.length());
mbedtls_md_finish(&ctx, hash.data());
mbedtls_md_free(&ctx);
return hash;
}
void setup()
{
Serial.begin(115200);
delay(2000);
std::string message = "www.luisllamas.es";
auto hash = generateSHA256(message);
Serial.print(message.c_str());
Serial.print("\nHash: ");
printBlockAsHex(hash);
}
void loop() {}
input
: Es la cadena de texto que queremos hashear.input_len
: Longitud de la cadena de entrada.output
: Un array de 32 bytes para almacenar el hash resultante.
mbedtls_sha256_context
: Estructura que almacena el estado del cálculo de hash.mbedtls_sha256_init
: Inicializa el contexto.mbedtls_sha256_starts
: Inicia el cálculo del hash (el segundo parámetro0
indica que usamos SHA-256).mbedtls_sha256_update
: Procesa la cadena de entrada.mbedtls_sha256_finish
: Finaliza el cálculo y almacena el hash en el buffer de salida.mbedtls_sha256_free
: Libera los recursos del contexto.
Al ejecutarlo tendréis algo como esto
Ejemplos prácticos
Verificación de integridad de datos
Supongamos que estás enviando datos desde un sensor a un servidor a través de Wi-Fi. Para asegurarte de que los datos no han sido alterados durante la transmisión, puedes calcular el hash SHA-256 de los datos antes de enviarlos y luego comparar el hash en el servidor.
// Calcular el hash de los datos del sensor
unsigned char sensor_data[] = {0x01, 0x02, 0x03, 0x04};
unsigned char hash[32];
mbedtls_sha256(sensor_data, sizeof(sensor_data), hash, 0);
// Enviar datos y hash al servidor
send_to_server(sensor_data, sizeof(sensor_data), hash, sizeof(hash));
En el servidor, puedes recalcular el hash y compararlo con el recibido para verificar la integridad.
Almacenamiento seguro de contraseñas
En lugar de almacenar contraseñas en texto plano, puedes almacenar su hash SHA-256. Esto protege las contraseñas incluso si el dispositivo es comprometido.
// Hash de la contraseña antes de almacenarla
const char *password = "mi_contraseña_secreta";
unsigned char password_hash[32];
mbedtls_sha256((const unsigned char *)password, strlen(password), password_hash, 0);
// Almacenar el hash en la memoria no volátil
store_password_hash(password_hash, sizeof(password_hash));
Autenticación de dispositivos
SHA-256 también se puede utilizar para autenticar dispositivos en una red. Por ejemplo, puedes generar un token basado en un secreto compartido y el tiempo actual, y luego enviar el hash SHA-256 del token para validar la identidad del dispositivo.
// Generar un token de autenticación
char token[64];
snprintf(token, sizeof(token), "%s%ld", "secreto_compartido", get_current_time());
unsigned char token_hash[32];
mbedtls_sha256((const unsigned char *)token, strlen(token), token_hash, 0);
// Enviar el hash para autenticación
send_authentication_token(token_hash, sizeof(token_hash));