Os programadores Python usam o hashing para transformar os dados de entrada em um valor de tamanho fixo. Esse valor representa os dados de forma exclusiva, e a técnica de hashing facilita a transmissão e o armazenamento seguro de várias formas de dados.

O hashing protege os dados contra acesso não autorizado e adulteração. É um ingrediente essencial nos casos de uso de integridade e segurança de dados.

Este artigo explora tudo o que você precisa saber sobre hashing em Python. Ele detalha os usos do hashing e destaca vários algoritmos de hashing que tornam seu código mais eficiente, seguro e confiável.

O que é hashing em Python?

Hashing converte dados de entrada, como uma string, arquivo ou objeto, em uma string de bytes de tamanho fixo. O hash ou digest representa a entrada de uma maneira única e reproduzível.

O hashing desempenha um papel significativo na detecção de manipulação de dados e no aumento da segurança. Ele pode calcular um valor de hash para um arquivo, mensagem ou outro tipo de dado. Um aplicativo armazena o hash de forma segura para verificar posteriormente que os dados não foram adulterados.

Um dos usos mais comuns do hashing na segurança é o armazenamento de senhas. O hashing é uma alternativa viável ao armazenamento de senhas de texto simples em um banco de dados. Quando um usuário digita sua senha, o sistema faz o hash antes de armazená-la no banco de dados. Se um hacker acessar o banco de dados, ele descobrirá que a senha é difícil de ser roubada.

As funções de hashing do Python tornam tudo isso possível. Essas funções matemáticas permitem que um aplicativo manipule dados em valores de hash.

Como criar uma função de hashing eficaz

Uma função de hash deve atender aos seguintes critérios para ser eficaz e segura:

  • Determinística – Dada a mesma entrada, a função deve sempre retornar a mesma saída.
  • Eficiente – Deve ser computacionalmente eficiente ao calcular o valor de hash de uma determinada entrada.
  • Resistente a colisões – A função deve minimizar a chance de duas entradas gerarem o mesmo valor de hash.
  • Uniforme – As saídas da função devem ser distribuídas uniformemente no intervalo de valores de hash possíveis.
  • Não inversível – Deve ser improvável que um computador calcule o valor de entrada da função com base no valor de hash.
  • Não previsível – Prever os resultados da função deve ser um desafio, dado um conjunto de entradas.
  • Sensível a alterações de entrada – A função deve ser sensível a pequenas diferenças na entrada. Pequenas alterações devem causar uma grande diferença no valor de hash resultante.

Casos de uso de hashing

Quando você tiver uma função de hashing adequada com todas essas características, poderá aplicá-la a vários casos de uso. As funções de hashing funcionam bem para:

  • Armazenamento de senhas – O hashing é uma das melhores maneiras de armazenar senhas de usuários em sistemas modernos. O Python combina vários módulos para fazer hash e proteger as senhas antes de armazená-las em um banco de dados.
  • Armazenamento em cache – O hashing armazena a saída de uma função para economizar tempo ao chamá-la posteriormente.
  • Recuperação de dados – O Python usa uma tabela de hash com uma estrutura de dados de dicionário integrada para recuperar rapidamente os valores por chave.
  • Assinaturas digitais – O hashing pode verificar a autenticidade das mensagens que têm assinaturas digitais.
  • Verificações de integridade de arquivos – O hashing pode verificar a integridade de um arquivo durante sua transferência e download.

Função de hashing integrada do Python

A função de hashing integrada do Python, hash(), retorna um valor inteiro que representa o objeto de entrada. Em seguida, o código usa o valor de hash resultante para determinar o local do objeto na tabela de hash. Essa tabela de hash é uma estrutura de dados que implementa dicionários e conjuntos.

O código abaixo demonstra como a função hash() funciona:

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)

Se salvarmos esse código em um arquivo chamado hash.py, poderemos executá-lo (e ver a saída) da seguinte forma:

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

Vamos executar isso novamente:

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

O valor do hash é diferente quando invocado uma segunda vez porque as versões recentes do Python (versões 3.3 e posteriores), por padrão, aplicam uma semente de hash aleatória para essa função. A semente muda em cada invocação do Python. Em uma única instância, os resultados serão idênticos.

Por exemplo, vamos colocar esse código em nosso arquivo 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)

Quando executado, você verá algo parecido com isto:

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

Limitações do hashing

Embora a função hash do Python seja promissora para vários casos de uso, suas limitações a tornam inadequada para fins de segurança. Veja como:

  • Ataques de colisão – Uma colisão ocorre quando duas entradas diferentes produzem o mesmo valor de hash. Um invasor pode usar o mesmo método de criação de entrada para contornar medidas de segurança que dependem de valores de hash para autenticação ou verificações de integridade de dados.
  • Tamanho de entrada limitado – Como as funções de hash produzem uma saída de tamanho fixo, independentemente do tamanho da entrada, uma entrada de tamanho maior do que a saída da função de hash pode causar uma colisão.
  • Previsibilidade – Uma função de hash deve ser determinística, fornecendo o mesmo resultado sempre que você fornecer a mesma entrada. Os invasores podem tirar proveito desse ponto fraco pré-compilando valores de hash para muitas entradas e, em seguida, comparando-os com hashes de valores-alvo para encontrar uma correspondência. Esse processo é chamado de ataque de tabela arco-íris.

Para evitar ataques e manter seus dados seguros, use algoritmos de hash seguros projetados para resistir a essas vulnerabilidades.

Uso do hashlib para hashing seguro em Python

Em vez de usar a função hash() nativa do Python, utilize hashlib para um hashing mais seguro. Este módulo do Python oferece uma variedade de algoritmos de hash para criptografar dados de forma segura. Estes algoritmos incluem MD5, SHA-1 e a família SHA-2 mais segura, que engloba SHA-256, SHA-384, SHA-512, entre outros.

MD5

O algoritmo criptográfico MD5, amplamente utilizado, revela um valor de hash de 128 bits. Use o código abaixo para gerar um hash MD5 usando o construtor md5 da hashlib:

import hashlib

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

A saída do código acima (em nosso arquivo hash.py) será consistente em todas as execuções:

b10a8db164e0754105b7a99be72e3fe5

Observação: O método hexdigest() no código acima retorna o hash em um formato hexadecimal seguro para qualquer apresentação não binária (como e-mail).

SHA-1

A função de hash SHA-1 protege os dados criando um valor de hash de 160 bits. Use o código abaixo com o construtor sha1 para o hash SHA-1 do módulo hashlib:

import hashlib

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

A saída do código acima:

0a4d55a8d778e5022fab701977c5d840bbc486d0

SHA-256

Há várias opções de hash na família SHA-2. O construtor hashlib SHA-256 gera uma versão mais segura dessa família com um valor de hash de 256 bits.

Os programadores costumam usar o SHA-256 para criptografia, como assinaturas digitais ou códigos de autenticação de mensagens. O código abaixo demonstra como gerar um hash SHA-256:

import hashlib

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

A saída do código acima:

a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

SHA-384

SHA-384 é um valor de hash de 384 bits. Os programadores costumam usar a função SHA-384 em aplicativos que precisam de mais segurança de dados.

Com base nos exemplos anteriores, você provavelmente pode adivinhar que esta é uma instrução que gerará um hash SHA-384:

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

SHA-512

O SHA-512 é o membro mais seguro da família SHA-2. Ele gera um valor de hash de 512 bits. Os programadores o utilizam para aplicativos de alto rendimento, como a verificação da integridade dos dados. O código abaixo mostra como gerar um hash SHA-512 com o módulo hashlib no Python:

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

Como escolher um algoritmo de hashing

Como esses algoritmos são diferentes, selecione o algoritmo de hashing com base no caso de uso e nos requisitos de segurança. Aqui estão algumas etapas que você deve seguir:

  • Entenda o caso de uso – O caso de uso determina o tipo de algoritmo a ser usado. Por exemplo, ao armazenar dados confidenciais, como senhas, o algoritmo de hash deve proteger contra ataques de força bruta.
  • Considere suas necessidades de segurança – Os requisitos de segurança do seu caso de uso dependem do tipo de dados que você pretende armazenar, e eles determinam qual algoritmo escolher. Por exemplo, um algoritmo de hashing robusto é mais adequado para armazenar informações altamente sensíveis.
  • Pesquise os algoritmos de hashing disponíveis – Explore cada tipo de hashing para entender seus pontos fortes e fracos. Essas informações ajudam você a selecionar a melhor opção para o seu caso de uso.
  • Avalie o algoritmo de hashing selecionado – Depois que você escolher um algoritmo de hashing, avalie se ele atende aos seus requisitos de segurança. Esse processo pode envolver testes contra ataques ou vulnerabilidades conhecidas.
  • Implemente e teste o algoritmo de hashing – Por fim, implemente e teste o algoritmo minuciosamente para garantir que ele funcione de forma correta e segura.

Como usar o hashing para armazenamento de senhas

O hashing tem excelente potencial para armazenar senhas, um componente essencial da segurança cibernética.

O ideal é que o aplicativo faça hash e armazene as senhas em um banco de dados seguro para evitar acesso não autorizado e violações de dados. No entanto, o hash sozinho pode não ser suficiente para proteger as informações. As senhas com hash ainda são suscetíveis a ataques de força bruta e de dicionário. Os hackers geralmente usam essas práticas para adivinhar senhas e obter acesso não autorizado às contas.

Uma maneira mais segura de usar hashing para o armazenamento de senhas envolve a técnica de salting. Salting adiciona strings ou caracteres únicos e aleatórios a cada senha antes de transformá-la em hash. O salt é único para cada senha, e o aplicativo o armazena junto com a senha em hash no banco de dados.

Sempre que um usuário faz login, o aplicativo recupera o sal do banco de dados, adiciona-o à senha inserida e, em seguida, faz o hash do sal e da senha combinados.

Se um invasor ganhar acesso ao banco de dados, ele terá que calcular o hash para cada senha e cada possível valor de salt. Salting torna esses ataques mais complexos, sendo uma técnica útil para desencorajar ataques de dicionário.

O módulo secrets do Python facilita o salting. Esse módulo gera salts aleatórios, armazenando senhas de forma segura e gerenciando tokens e chaves criptográficas.

O código abaixo usa a biblioteca hashlib e o módulo secrets para proteger ainda mais as senhas dos usuários:

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

Como usar o hashing para verificações de integridade de dados

O hashing também ajuda a verificar a integridade dos dados e a proteger os dados transmitidos contra modificações e adulterações. Essa técnica de quatro etapas usa uma função de hash criptográfico para dar ao arquivo um valor de hash exclusivo.

Primeiro, selecione a função de hash apropriada e use para gerar um valor de hash para os dados de entrada. Armazene esse valor de hash e use para comparação quando necessário. Sempre que você precisar verificar a integridade dos dados, o aplicativo gerará o valor de hash dos dados atuais usando a mesma função de hash. Em seguida, o aplicativo compara o novo valor de hash com o valor armazenado para garantir que eles sejam idênticos. Em caso afirmativo, os dados não serão corrompidos.

O valor de hash é exclusivo, e até mesmo uma pequena alteração nos dados de entrada aciona um valor de hash significativamente diferente. Isso facilita a detecção de alterações ou modificações não autorizadas nos dados transmitidos.

As etapas abaixo demonstram o uso de uma função de hash para verificações de integridade de dados.

Etapa 1: Importe o módulo hashlib

import hashlib

Etapa 2: Use um algoritmo de 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()

Etapa 3: Chame a função e passe o caminho do arquivo

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

Etapa 4: Gere hashes para o arquivo original e para o arquivo transmitido ou 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)

Etapa 5: Compare os dois hashes

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

Resumo

O hashing é inestimável para a integridade de dados e a segurança de senhas. Você aproveita ao máximo uma função de hashing quando implementa técnicas de hashing seguras, como o uso do módulo hashlib e salting.

Essas técnicas ajudam a prevenir ataques do tipo rainbow, colisões e outras vulnerabilidades de segurança que afetam o hashing. Programadores frequentemente utilizam essas técnicas com funções de hashing em Python para garantir a integridade dos dados de arquivos e armazenar senhas de forma segura.

Agora que você aprendeu mais sobre as técnicas de hashing em Python, use-as para melhorar a segurança do seu próprio aplicativo. Explore mais artigos sobre Python no blog da Kinsta para aumentar sua experiência e, em seguida, considere implantar seu próximo aplicativo Python na plataforma de hospedagem de aplicativos da Kinsta.

Steve Bonisteel Kinsta

Steve Bonisteel is a Technical Editor at Kinsta who began his writing career as a print journalist, chasing ambulances and fire trucks. He has been covering Internet-related technology since the late 1990s.