hashlib – Cifrar con los algoritmos MD5 y SHA

hashlib – Cifrar con los algoritmos MD5 y SHA

Introducción

Actualmente cualquier proyecto que requiera el almacenamiento de datos de un usuario hace uso de uno o múltiples algoritmos para llevar a cabo un cifrado, que permite ocultar o proteger determinada información. En la mayoría de los sitios que requieren de un registro las contraseñas son cifradas y se almacena un hash (el resultado) en lugar del texto original.

Existen diversos y muy variados algoritmos para realizar dicha acción; esta entrada cubre la utilización del MD5 (Message-Digest Algorithm 5) y las familias SHA (Secure Hash Algorithm), BLAKE y SHAKE.

El módulo

El módulo hashlib pertenece a la librería estándar y permite realizar cifrados directamente desde Python con los algoritmos BLAKE, SHAKE, SHA1, SHA224, SHA256, SHA384, SHA512 y MD5. Este último también cuenta con un módulo obsoleto que permite su implementación llamado md5 (solo disponible en Python 2).

Es un módulo pequeño, lo que conlleva a una explicación pequeña. Comencemos importándolo.

import hashlib

La función new() retorna un nuevo objeto de la clase hash implementando la función (hash) especificada.

h = hashlib.new("hash", "cadena")

En donde el primer parámetro debe ser una cadena como «sha1», «md5», «sha224» y el segundo cualquier tipo de cadena que queramos cifrar.

Un ejemplo para cifrar con SHA1 e imprimir el resultado en pantalla:

h = hashlib.new("sha1", b"www.recursospython.com - Recursos Python")
print(h.digest())

h = hashlib.new("sha1", "www.recursospython.com - Recursos Python")
print h.digest()

El método digest() retorna la cadena cifrada en binario. Para obtenerla en hexadecimal, existe la función hexdigest().

Nótese que para los algoritmos SHAKE, los métodos digest() y hexdigest() requieren como argumento la longitud de la cadena resultante (por ejemplo, 128 o 256).

print(h.hexdigest())

print h.hexdigest()

Imprime:

25a2d7ca3a90b404ffb8bc0e67d99358d43d7b40

Las siguientes funciones son más rápidas que new() y realizan exactamente lo mismo:

  • blake2b()
  • blake2s()
  • md5()
  • sha1()
  • sha224()
  • sha256()
  • sha3_224()
  • sha3_256()
  • sha3_384()
  • sha3_512()
  • sha512()
  • shake_128()
  • shake_256()

Equivalen a:

  • new("blake2b")
  • new("blake2s")
  • new("md5")
  • new("sha1")
  • etc.

Tu plataforma puede disponer de una mayor cantidad de algoritmos, pero las especificadas anteriormente están garantizadas.

De esta manera, retomando el ejemplo anterior, aplicamos el método más rápido:

h = hashlib.sha1(b"www.recursospython.com - Recursos Python")
print(h.digest(), h.hexdigest())

h = hashlib.sha1("www.recursospython.com - Recursos Python")
print h.digest(), h.hexdigest()

También como:

h = hashlib.sha1()
h.update(b"www.recursospython.com ")
h.update(b"- Recursos Python")
print(h.digest(), h.hexdigest())

h = hashlib.sha1()
h.update("www.recursospython.com ")
h.update("- Recursos Python")
print h.digest(), h.hexdigest()

Ambos ejemplos resultan en lo mismo. La función update() actualiza el objeto h añadiendo una nueva cadena. Reiteradas llamadas son equivalentes a una sola con la concatenación de las cadenas. Por lo tanto, el código anterior es igual a:

h.update(b"www.recursospython.com - Recursos Python")

h.update("www.recursospython.com - Recursos Python")

No obstante, dividir el cifrado de la información en varios bloques es particularmente útil cuando el tamaño de los datos es muy grande y no puede ser cargado completamente en memoria (por ejemplo, al cifrar el contenido de un archivo; véase Buscador multiplataforma de archivos iguales).

La colección hashlib.algorithms_guaranteed provee los nombres de los algoritmos soportados por el módulo que están presentes en todas las distribuciones del lenguaje, por lo que con el siguiente código podemos probar la eficacia de cada una de las funciones:

for algorithm in hashlib.algorithms_guaranteed:
    print(algorithm)
    h = hashlib.new(algorithm)
    h.update(b"www.recursospython.com - Recursos Python")
    try:
        print(h.hexdigest())
    except TypeError:
        # Algoritmo SHAKE requiere la longitud como argumento.
        print(h.hexdigest(128))

for algorithm in hashlib.algorithms:
    print algorithm
    h = hashlib.new(algorithm)
    h.update("www.recursospython.com - Recursos Python")
    print h.hexdigest()

Que imprime (en Python 2) algo similar a esto:

md5
2ba65b95d174a48ac2c0878a574863ea
sha1
25a2d7ca3a90b404ffb8bc0e67d99358d43d7b40
sha224
cfe43b9ed628eef882e72b9e14a838ccc566b10beb0a5f8c275f3c6e
sha256
fee1dbcfc0f1ac2db05e9bf2602c0765f6b6605f9db1ded035ccd3236206f417
sha384
0ecc9ed6231e1a705565b62c8ebf9fcce676aedda5c937721e9246e1928d79b5eda79c6efdfb0e3d
df518e8d3cc9005e
sha512
7f8a53b63443f044f04fec788c987fb7f846b106c4e6a0891b06f6fe8d90f4eb0d55a2942f9588d9
1a40b38282586e0c76ef3e28dae44470c4a992e3df0bde54

La clase

Métodos y atributos de la clase hash (retornada por new() o cualquiera de las demás funciones hash: md5(), sha1(), etc.) con excepción de update() que fue explicado anteriormente.

Atributos

hash.digest_size
El tamaño del hash en bytes

hash.block_size
El tamaño del bloque interno del algoritmo hash en bytes.

Funciones

hash.digest()
Retorna el «digest» de las cadenas pasadas por el método update(). Es una cadena de digest_size bytes que contiene carácteres no ASCII, incluyendo bytes nulos.

hash.hexdigest()
Como digest() excepto que el valor de retorno es una cadena del doble de tamaño, conteniendo únicamente dígitos hexadecimales. Este método debe ser utilizado para aplicar el valor en un email o en otros entornos no binarios.

hash.copy()
Retorna una copia del objeto hash.

Ejemplos

El siguiente script imprime el hash correspondiente para cada uno de los archivos contenidos en la carpeta en donde se encuentra ubicado, aplicando los distintos algoritmos que presenta hashlib.

import hashlib
from os import listdir
from os.path import isdir, islink

for filename in listdir("."):
    if not isdir(filename) and not islink(filename):
        try:
            f = open(filename, "rb")
        except IOError as e:
            print(e)
        else:
            data = f.read()
            f.close()
            print("** %s **" % filename)
            for algorithm in hashlib.algorithms_guaranteed:
                h = hashlib.new(algorithm)
                h.update(data)
                try:
                    hexdigest = h.hexdigest()
                except TypeError:
                    hexdigest = h.hexdigest(128)
                print("%s: %s" % (algorithm, hexdigest))
            print()

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import hashlib
from os import listdir
from os.path import isdir, islink

for filename in listdir("."):
    if not isdir(filename) and not islink(filename):
        try:
            f = open(filename, "r")
        except IOError as e:
            print(e)
        else:
            data = f.read()
            f.close()
            print "** %s **" % filename
            for algorithm in hashlib.algorithms_guaranteed:
                h = hashlib.new(algorithm)
                h.update(data)
                hexdigest = h.hexdigest()
                print "%s: %s" % (algorithm, hexdigest)
            print

Revisiones

Este artículo fue originalmente publicado en agosto del 2013 y actualizado en marzo de 2019.

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

17 comentarios.

  1. Saludos: Como se puede saber los haches restantes de este ejemplo:
    para:
    E435-A30C-24EC-4490-A23E-FE5C-470D-3E39
    me genera lo siguiente:
    35b34b0556702d5e3f14d3a8ed08f77b

    Entonces para: E435-A30C-24EC-4490-A23E-98DB-80FD-5549
    que sería lo que se genera:?

  2. Hola… cómo puedo hacer esto pero para cifrar ID en Python, en R lo hago de la siguiente manera:

    df$hash <- sapply(df$salting, digest, algo="xxhash64",
    seed= 123,
    ascii = TRUE,
    length= Inf)

    • Recursos Python says:

      Hola. Si entiendo bien tu pregunta, podrías hacer algo así:

      >>> import hashlib
      >>> key = b"hola"
      >>> hashmap = {hashlib.sha256(key).hexdigest(): "mundo"}

      Saludos!

  3. Victor Caraballo says:

    Y para hacerlo al reves como se haria?… Lo pienso usar para cifrado de claves de usuarios, y pienso usar como comparacion h.hexadigest() ==»texto». Pero al momento de pasar de hexadecimal a texto como se haria?

    No se si me explique. De todas formas bien tutorial me ayudo bastante (Y)

    • Recursos Python says:

      Hola. Como indiqué en otro comentario, estos algoritmos son irreversibles. No hay forma de obtener la cadena original. No obstante, no la necesitas para almacenar contraseñas, basta con comparar el texto cifrado de la base de datos con el que provee el usuario y chequear si coinciden.

      Un saludo.

      • exactamente como dice el de arriba encripta la contraseña que pasa el usuario y comparala con la que tienes guardada en la base de datos!

    • Recursos Python says:

      Claro, con cualquier archivo tal como se muestra en el último ejemplo. Si f es un fichero abierto, puedes obtener el cifrado MD5 utilizando hashlib.md5(f.read()).hexdigest().

      Saludos.

    • Recursos Python says:

      Hola Pamela. Este tipo de algoritmos son irreversibles, es decir, no hay forma de volver a obtener la información original. Si bien se han encontrado vulnerabilidades en el algoritmo MD5 (y a su vez en sus anteriores, MD4) es algo muy complejo. Sin embargo tienes soluciones como simple-crypt para encriptar y desencriptar información, y si quieres algo más complejo puedes visitar pycrypto.

      Un saludo y gracias por comentar.

    • Recursos Python says:

      Claro, siempre utilizo esa expresión para cifrar rápidamente pequeñas cadenas a través de la consola. Gracias por comentar, saludos.

Deja una respuesta