Los programadores de Python utilizan el hashing para transformar los datos de entrada en un valor de tamaño fijo. Este valor representa los datos de forma única, y la técnica de hashing facilita la transmisión y el almacenamiento seguro de diversas formas de datos.

El hashing protege los datos de accesos no autorizados y manipulaciones. Es un ingrediente esencial en los casos de uso de integridad y seguridad de los datos.

Este artículo explora todo lo que necesitas saber sobre el hashing en Python. Se sumerge en los usos del hashing y destaca varios algoritmos de hashing que hacen que tu código sea más eficiente, seguro y fiable.

¿Qué Es el Hashing en Python?

El hashing convierte los datos de entrada, como una cadena, un archivo o un objeto, en una cadena de bytes de tamaño fijo. El hash o resumen representa la entrada de una forma única y reproducible.

El hash desempeña un papel importante en la detección de la manipulación de datos y en la mejora de la seguridad. Puede calcular un valor hash para un archivo, mensaje u otros datos. Una aplicación almacena el hash de forma segura para verificar posteriormente que los datos no han sido manipulados.

Uno de los usos más comunes del hash en seguridad es el almacenamiento de contraseñas. El hash es una alternativa viable al almacenamiento de contraseñas en texto plano en una base de datos. Cuando un usuario introduce su contraseña, el sistema la somete a hashing antes de almacenarla en la base de datos. Si un hacker accede a la base de datos, comprobará que la contraseña es difícil de robar.

Las funciones hash de Python hacen que todo esto sea posible. Estas funciones matemáticas permiten a una aplicación manipular datos en valores hash.

Cómo Hacer una Función Hashing Eficaz

Una función hash debe cumplir los siguientes criterios para ser eficaz y segura:

  • Determinista — Dando la misma entrada, la función debe devolver siempre la misma salida.
  • Eficiente — Debe ser eficiente computacionalmente al calcular el valor hash de cualquier entrada dada.
  • Resistente a las colisiones — La función debe minimizar la posibilidad de que dos entradas obtengan el mismo valor hash.
  • Uniforme — Los resultados de la función deben distribuirse uniformemente en el rango de posibles valores hash.
  • No invertible —  Debe ser improbable que un ordenador calcule el valor de entrada de la función basándose en el valor hash.
  • No predecible — Predecir las salidas de la función debe ser un reto, si se dan un conjunto de entradas.
  • Sensible a los cambios de entrada — La función debe ser sensible a pequeñas diferencias en la entrada. Los cambios leves deben provocar una gran diferencia en el valor hash resultante.

Casos de Uso del Hashing

Una vez que tengas una función hashing adecuada con todas estas características, puedes aplicarla a varios casos de uso. Las funciones hashing funcionan bien para:

  • Almacenamiento de contraseñas — El hashing es una de las mejores formas de almacenar las contraseñas de los usuarios en los sistemas modernos. Python combina varios módulos para hacer hash y asegurar las contraseñas antes de almacenarlas en una base de datos.
  • Almacenamiento en caché — El hashing almacena la salida de una función para ahorrar tiempo al llamarla más tarde.
  • Recuperación de datos — Python utiliza una tabla hash con una estructura de datos de diccionario incorporada para recuperar rápidamente valores por clave.
  • Firmas digitales — El hashing puede verificar la autenticidad de los mensajes que tienen firmas digitales.
  • Comprobación de la integridad de los archivos — El hashing puede comprobar la integridad de un archivo durante su transferencia y descarga.

Función Hashing Integrada en Python

La función hashing integrada de Python, hash(), devuelve un valor entero que representa el objeto de entrada. El código utiliza el valor hash resultante para determinar la ubicación del objeto en la tabla hash. Esta tabla hash es una estructura de datos que implementa diccionarios y conjuntos.

El siguiente código muestra cómo funciona la función hash():

my_string = "hello world"

# Calculate the hash value of the string
hash_value = hash(my_string)

# Print the string and its hash value
print("String: ", my_string)
print("Hash value: ", hash_value)

Si guardamos ese código en un archivo llamado hash.py, podemos ejecutarlo (y ver la salida) así:

% python3 hash.py
String:  hello world
Hash value:  2213812294562653681

Ejecutémoslo de nuevo:

% python3 hash.py
String:  hello world
Hash value:  -631897764808734609

El valor hash es diferente cuando se invoca por segunda vez porque las versiones recientes de Python (a partir de la 3.3) aplican, por defecto, una semilla hash aleatoria para esta función. La semilla cambia en cada invocación de Python. Dentro de una misma instancia, los resultados serán idénticos.

Por ejemplo, pongamos este código en nuestro archivo hash.py:

my_string = "hello world"

# Calculate 2 hash values of the string
hash_value1 = hash(my_string)
hash_value2 = hash(my_string)

# Print the string and its hash values
print("String: ", my_string)
print("Hash value 1: ", hash_value1)
print("Hash value 2: ", hash_value2)

Cuando se ejecuta, vemos algo así

String: hello world
Hash value 1:  -7779434013116951864
Hash value 2:  -7779434013116951864

Limitaciones del Hashing

Aunque la función hash de Python es prometedora para diversos casos de uso, sus limitaciones la hacen inadecuada para fines de seguridad. Aquí te explicamos cómo:

  • Ataques de colisión — Se produce una colisión cuando dos entradas diferentes producen el mismo valor hash. Un atacante podría utilizar el mismo método de generación de entradas para eludir las medidas de seguridad que se basan en valores hash para la autenticación o la comprobación de la integridad de los datos.
  • Tamaño de entrada limitado — Dado que las funciones hash producen una salida de tamaño fijo independientemente del tamaño de la entrada, una entrada de tamaño mayor que la salida de la función hash puede provocar una colisión.
  • Previsibilidad — Una función hash debe ser determinista, dando la misma salida cada vez que proporciones la misma entrada. Los atacantes podrían aprovecharse de esta debilidad precompilando valores hash para muchas entradas, y comparándolos después con los hashes de los valores objetivo para encontrar una coincidencia. Este proceso se denomina ataque de tabla arco iris.

Para evitar ataques y mantener tus datos a salvo, utiliza algoritmos hash seguros diseñados para resistir estas vulnerabilidades.

Utilizar hashlib para un Hashing Seguro en Python

En lugar de utilizar el módulo incorporado de Python hash(), utiliza hashlib para un hashing más seguro. Este módulo de Python ofrece una variedad de algoritmos hash para hacer hash de datos de forma segura. Estos algoritmos incluyen MD5, SHA-1 y la familia más segura SHA-2, que incluye SHA-256, SHA-384, SHA-512 y otros.

MD5

El algoritmo criptográfico MD5, ampliamente utilizado, revela un valor hash de 128 bits. Utiliza un código como el siguiente para generar un hash MD5 utilizando el constructor md5 de hashlib:

import hashlib

text = "Hello World"
hash_object = hashlib.md5(text.encode())
print(hash_object.hexdigest())

El resultado de lo anterior (en nuestro archivo hash.py ) será consistente en todas las invocaciones:

b10a8db164e0754105b7a99be72e3fe5

Nota: El método hexdigest() del código anterior devuelve el hash en un formato hexadecimal seguro para cualquier presentación no binaria (como el correo electrónico).

SHA-1

La función hash SHA-1 asegura los datos haciendo un valor hash de 160 bits. Utiliza el código siguiente con el constructor sha1 para el hash SHA-1 del módulo hashlib:

import hashlib

text = "Hello World"
hash_object = hashlib.sha1(text.encode())
print(hash_object.hexdigest())

La salida de lo anterior:

0a4d55a8d778e5022fab701977c5d840bbc486d0

SHA-256

Hay varias opciones de hash en la familia SHA-2. El constructor hashlib SHA-256 genera una versión más segura de esa familia con un valor hash de 256 bits.

Los programadores suelen utilizar SHA-256 para criptografía, como firmas digitales o códigos de autenticación de mensajes. El siguiente código muestra cómo generar un hash SHA-256:

import hashlib

text = "Hello World"
hash_object = hashlib.sha256(text.encode())
print(hash_object.hexdigest())

El resultado de lo anterior:

a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

SHA-384

SHA-384 es un valor hash de 384 bits. Los programadores suelen utilizar la función SHA-384 en aplicaciones que necesitan una mayor seguridad de los datos.

Basándote en los ejemplos anteriores, probablemente puedas adivinar que ésta es una sentencia que generará un hash SHA-384:

hash_object = hashlib.sha384(text.encode())

SHA-512

SHA-512 es el miembro más seguro de la familia SHA-2. Genera un valor hash de 512 bits. Los programadores lo utilizan para aplicaciones de alto rendimiento, como la comprobación de la integridad de los datos. El código siguiente muestra cómo generar un hash SHA-512 con el módulo hashlib de Python:

hash_object = hashlib.sha512(text.encode())

Cómo Elegir un Algoritmo Hashing

Como estos algoritmos son distintos, selecciona tu algoritmo hashing en función de tu caso de uso y de sus requisitos de seguridad. Aquí tienes algunos pasos a seguir:

  • Comprende el caso de uso — Tu caso de uso determina qué tipo de algoritmo utilizar. Por ejemplo, al almacenar datos sensibles, como contraseñas, tu algoritmo hashing debe proteger contra ataques de fuerza bruta.
  • Considera tus requisitos de seguridad — Los requisitos de seguridad de tu caso de uso dependen del tipo de datos que pretendas almacenar, y determinan qué tipo de algoritmo elegir. Por ejemplo, un algoritmo hashing robusto es mejor para almacenar información muy sensible.
  • Investiga los algoritmos hashing disponibles — Explora cada tipo de hashing para comprender sus puntos fuertes y débiles. Esta información te ayuda a seleccionar la mejor opción para tu caso de uso.
  • Evalúa el algoritmo hashing seleccionado — Una vez elegido un algoritmo hashing, evalúa si cumple tus requisitos de seguridad. Este proceso puede implicar probarlo contra ataques o vulnerabilidades conocidos.
  • Implementa y prueba el algoritmo hashing — Por último, implementa y prueba el algoritmo a fondo para asegurarte de que funciona correctamente y con seguridad.

Cómo Utilizar el Hashing para Almacenar Contraseñas

El hashing tiene un potencial excelente para almacenar contraseñas, un componente crítico de la ciberseguridad.

Lo ideal es que la aplicación realice el hash y almacene las contraseñas en una base de datos segura para evitar accesos no autorizados y violaciones de datos. Sin embargo, el hashing por sí solo podría no ser suficiente para proteger la información. Las contraseñas troceadas siguen siendo susceptibles a los ataques de fuerza bruta y de diccionario. Los hackers suelen utilizar estas prácticas para adivinar las contraseñas y obtener acceso no autorizado a las cuentas.

Una forma más segura de utilizar el hashing para almacenar contraseñas es la técnica del salting. El salting añade cadenas o caracteres aleatorios y únicos a cada contraseña antes de aplicar el hashing. El salt es único para cada contraseña, y la aplicación lo almacena junto a la contraseña hash en la base de datos.

Cada vez que un usuario se conecta, la aplicación recupera el salt de la base de datos, lo añade a la contraseña introducida y, a continuación, aplica el hash a la combinación de salt y contraseña.

Si un atacante consigue acceder a la base de datos, deberá calcular el hash para cada contraseña y cada posible valor del salt. El salting hace que estos ataques sean más complejos, por lo que es una técnica útil para disuadir los ataques de diccionario.

El módulo secrets de Python facilita el salting. Este módulo genera salts aleatorios, almacenando contraseñas de forma segura y gestionando tokens y claves criptográficas.

El siguiente código utiliza la biblioteca hashlib y el módulo secrets para asegurar aún más las contraseñas de los usuarios:

import hashlib
import secrets

# Generate a random salt using the secrets module
salt = secrets.token_hex(16)

# Get the user's password from input
password = input("Enter your password: ")

# Hash the password using the salt and the SHA-256 algorithm
hash_object = hashlib.sha256((password + salt).encode())

# Get the hexadecimal representation of the hash
hash_hex = hash_object.hexdigest()

# Store the salt and hash_hex in your database

Cómo Utilizar el Hashing para Comprobar la Integridad de los Datos

El hashing también ayuda a comprobar la integridad de los datos y a proteger los datos transmitidos de modificaciones y manipulaciones. Esta técnica de cuatro pasos utiliza una función hash criptográfica para dar al archivo un valor hash único.

Primero, selecciona la función hash adecuada y utilízala para generar un valor hash para los datos de entrada. Almacena ese valor hash y utilízalo para compararlo cuando sea necesario. Siempre que necesites verificar la integridad de los datos, la aplicación genera el valor hash de los datos actuales utilizando la misma función hash. Después, la aplicación compara el nuevo valor hash con el valor almacenado para asegurarse de que son idénticos. Si es así, los datos están intactos.

El valor hash es único, e incluso un cambio minúsculo en los datos de entrada desencadena un valor hash significativamente diferente. Esto facilita la detección de cualquier cambio o modificación no autorizados de los datos transmitidos.

Los pasos siguientes demuestran el uso de una función hash para comprobar la integridad de los datos.

Paso 1: Importar el módulo hashlib

import hashlib

Paso 2: Utilizar un Algoritmo Hash hashlib

def generate_hash(file_path):

    # Open the file in binary mode
    with open(file_path, "rb") as f:

        # Read the contents of the file
        contents = f.read()

        # Generate the SHA-256 hash of the contents
        hash_object = hashlib.sha256(contents)

        # Return the hexadecimal representation of the hash
        return hash_object.hexdigest()

Paso 3: Llama a la Función e Introduce la Ruta del Archivo

file_path = "path/to/my/file.txt"
hash_value = generate_hash(file_path)
print(hash_value)

Paso 4: Generar Hashes para el Archivo Original y el Archivo Transmitido o Modificado

# Generate the hash of the original file
original_file_path = "path/to/my/file.txt"
original_file_hash = generate_hash(original_file_path)

# Transmit or modify the file (for example, by copying it to a different location)
transmitted_file_path = "path/to/transmitted/file.txt"

# Generate the hash of the transmitted file
transmitted_file_hash = generate_hash(transmitted_file_path)

Paso 5: Comparar los Dos Hashes

if original_file_hash == transmitted_file_hash:
    print("The file has not been tampered with")
else:
    print("The file has been tampered with")

Resumen

El hashing tiene un valor incalculable para la integridad de los datos y la seguridad de las contraseñas. Sacarás el máximo partido de una función hashing cuando pongas en práctica técnicas de hashing seguras, como el uso del módulo hashlib y el salting.

Estas técnicas ayudan a evitar los ataques arco iris, los ataques de colisión y otras vulnerabilidades de seguridad que afectan al hashing. Los programadores suelen utilizar estas técnicas con funciones hashing en Python para garantizar la integridad de los datos de los archivos y almacenar contraseñas de forma segura.

Ahora que has aprendido más sobre las técnicas de hashing en Python, utilízalas para mejorar la seguridad de tu propia aplicación. Explora más artículos sobre Python en el blog de Kinsta para ampliar tus conocimientos, y luego considera desplegar tu próxima aplicación Python en la plataforma de Alojamiento de Aplicaciones de Kinsta.

Steve Bonisteel Kinsta

Steve Bonisteel es un Editor Técnico de Kinsta que comenzó su carrera de redactor como periodista de prensa escrita, persiguiendo ambulancias y camiones de bomberos. Lleva tratando temas relacionados con la tecnología de Internet desde finales de la década de 1990.