Este guia detalha como gerar hashes seguros utilizando os recursos integrados do módulo `hashlib` do Python.
Compreender a relevância do hashing e a capacidade de calcular hashes seguros de forma programática pode ser vantajoso, mesmo que sua área não seja a segurança de aplicações. Mas por que isso?
Ao trabalhar em projetos com Python, é provável que você se depare com situações onde precisa proteger senhas e outros dados sensíveis armazenados em bancos de dados ou arquivos de código. Nesses casos, aplicar um algoritmo de hash aos dados confidenciais e armazenar o hash resultante, em vez dos próprios dados, é uma prática mais segura.
Neste tutorial, exploraremos o conceito de hashing e suas diferenças em relação à criptografia. Analisaremos também as características das funções de hash seguras. Em seguida, utilizaremos algoritmos de hash comuns para calcular o hash de texto simples em Python, empregando o módulo interno `hashlib`.
Prepare-se para desvendar esses mistérios e muito mais!
O que é Hashing?
O processo de hashing converte uma sequência de texto (mensagem) em uma saída de tamanho fixo, conhecida como hash. Isso significa que, para um dado algoritmo de hash, o comprimento do hash resultante é constante, independentemente do tamanho da entrada. Mas como isso se distingue da criptografia?
Na criptografia, um texto simples é transformado em um formato ilegível (criptografado) usando um algoritmo específico. Em seguida, esse texto criptografado pode ser revertido para o formato original através de um processo de descriptografia.
O hashing, por sua vez, funciona de forma diferente. Enquanto a criptografia é um processo reversível, o hashing não é. Ou seja, não é possível recuperar a mensagem original a partir do hash.
Características Essenciais das Funções de Hash
Vamos examinar as propriedades que uma função de hash segura deve possuir:
- Determinística: Para uma mesma entrada, o hash gerado será sempre o mesmo.
- Resistência à Pré-Imagem: É extremamente difícil, ou praticamente impossível, deduzir a mensagem original a partir de seu hash.
- Resistência a Colisões: É muito difícil encontrar duas mensagens diferentes que gerem o mesmo hash.
- Resistência à Segunda Pré-Imagem: Dado um hash e uma mensagem, é inviável encontrar outra mensagem que produza o mesmo hash.
O Módulo hashlib
do Python
O módulo interno `hashlib` do Python oferece implementações de diversos algoritmos de resumo de mensagens e hash, incluindo os populares SHA e MD5.
Para utilizar as funções e construtores do módulo `hashlib`, você deve importá-lo em seu ambiente de trabalho:
import hashlib
O módulo `hashlib` disponibiliza as constantes `algorithms_available` e `algorithms_guaranteed`. A primeira lista todos os algoritmos que são suportados e a segunda, os que são garantidamente implementados na plataforma atual. `algorithms_guaranteed` é, portanto, um subconjunto de `algorithms_available`.
Inicie o REPL do Python, importe o `hashlib` e verifique o conteúdo das constantes:
>>> hashlib.algorithms_available
# Saída {'md5', 'md5-sha1', 'sha3_256', 'shake_128', 'sha384', 'sha512_256', 'sha512', 'md4', 'shake_256', 'whirlpool', 'sha1', 'sha3_512', 'sha3_384', 'sha256', 'ripemd160', 'mdc2', 'sha512_224', 'blake2s', 'blake2b', 'sha3_224', 'sm3', 'sha224'}
>>> hashlib.algorithms_guaranteed
# Saída {'md5', 'shake_256', 'sha3_256', 'shake_128', 'blake2b', 'sha3_224', 'sha3_384', 'sha384', 'sha256', 'sha1', 'sha3_512', 'sha512', 'blake2s', 'sha224'}
Conforme esperado, `algorithms_guaranteed` é um subconjunto de `algorithms_available`.
Criando Objetos Hash em Python
Vamos agora aprender como criar objetos hash em Python. Calcularemos o hash SHA256 de uma string, utilizando:
- O construtor genérico `new()`
- Construtores específicos do algoritmo
Usando o Construtor new()
Começaremos inicializando a string que será utilizada como mensagem:
>>> message = "etechpt.com is awesome!"
Para instanciar o objeto hash, podemos utilizar o construtor `new()` e fornecer o nome do algoritmo desejado:
>>> sha256_hash = hashlib.new("SHA256")
Em seguida, chamamos o método `update()` no objeto hash, passando a mensagem como argumento:
>>> sha256_hash.update(message)
Ao fazer isso, você se deparará com um erro. Isso ocorre porque os algoritmos de hash operam apenas sobre sequências de bytes.
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Unicode-objects must be encoded before hashing
Para converter a string em uma sequência de bytes, utilize o método `encode()` da string original. Em seguida, utilize essa sequência no método `update()`. Feito isso, invoque o método `hexdigest()` para obter o hash SHA256 resultante.
sha256_hash.update(message.encode()) sha256_hash.hexdigest() # Saída:'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
Em vez de usar o método `encode()`, você pode definir a mensagem diretamente como uma string de bytes, precedendo-a com `b`:
message = b"etechpt.com is awesome!" sha256_hash.update(message) sha256_hash.hexdigest() # Saída: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
O hash resultante é o mesmo que o anterior, comprovando o caráter determinístico das funções de hash.
Além disso, pequenas alterações na mensagem geram mudanças drásticas no hash, um fenômeno conhecido como “efeito avalanche”.
Para demonstrar isso, trocaremos o `a` em `awesome` por `A` e recalcularemos o hash:
message = "etechpt.com is Awesome!" h1 = hashlib.new("SHA256") h1.update(message.encode()) h1.hexdigest() # Saída: '3c67f334cc598912dc66464f77acb71d88cfd6c8cba8e64a7b749d093c1a53ab'
Como vemos, o hash mudou completamente.
Usando o Construtor Específico do Algoritmo
No exemplo anterior, utilizamos o construtor genérico `new()` e fornecemos `”SHA256″` como nome do algoritmo.
Alternativamente, podemos utilizar diretamente o construtor `sha256()`:
sha256_hash = hashlib.sha256() message= "etechpt.com is awesome!" sha256_hash.update(message.encode()) sha256_hash.hexdigest() # Saída: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
O hash de saída é idêntico ao obtido anteriormente, com a string `”etechpt.com is awesome!”`.
Explorando Atributos dos Objetos Hash
Objetos hash possuem atributos úteis, tais como:
- O atributo `digest_size` indica o tamanho do resumo em bytes. Por exemplo, o algoritmo SHA256 gera um hash de 256 bits, equivalentes a 32 bytes.
- O atributo `block_size` refere-se ao tamanho do bloco utilizado no algoritmo de hash.
- O atributo `name` contém o nome do algoritmo que pode ser utilizado no construtor `new()`. Este atributo pode ser útil quando os objetos hash não possuem nomes descritivos.
Podemos verificar esses atributos no objeto `sha256_hash` criado anteriormente:
>>> sha256_hash.digest_size 32 >>> sha256_hash.block_size 64 >>> sha256_hash.name 'sha256'
A seguir, veremos aplicações práticas do hashing com o módulo `hashlib` do Python.
Aplicações Práticas do Hashing
Verificação da Integridade de Software e Arquivos
Desenvolvedores baixam e instalam pacotes de software com frequência. No entanto, nem todas as fontes de software são confiáveis. Ao lado do link de download, muitas vezes, encontra-se o hash (ou checksum) do arquivo. Comparar o hash do arquivo baixado com o hash oficial garante a integridade do arquivo.
Essa abordagem pode ser aplicada a arquivos em seu computador. Mesmo pequenas modificações em um arquivo resultam em grandes alterações no hash. Ao verificar o hash, você pode garantir que o arquivo não foi alterado.
Vejamos um exemplo simples. Crie o arquivo de texto `my_file.txt` em seu diretório de trabalho e adicione algum conteúdo:
$ cat my_file.txt This is a sample text file. We are going to compute the SHA256 hash of this text file and also check if the file has been modified by recomputing the hash.
Em seguida, abra o arquivo em modo de leitura binária (`rb`), leia seu conteúdo e calcule o hash SHA256:
>>> import hashlib >>> with open("my_file.txt","rb") as file: ... file_contents = file.read() ... sha256_hash = hashlib.sha256() ... sha256_hash.update(file_contents) ... original_hash = sha256_hash.hexdigest()
Nesse caso, a variável `original_hash` armazena o hash do arquivo `my_file.txt` no estado atual.
>>> original_hash # Saída: '53bfd0551dc06c4515069d1f0dc715d002d451c8799add29f3e5b7328fda9f8f'
Agora, modifique o arquivo `my_file.txt`. Remova, por exemplo, o espaço em branco adicional antes da palavra “going”.
Calcule o hash novamente e armazene-o na variável `computed_hash`:
>>> import hashlib >>> with open("my_file.txt","rb") as file: ... file_contents = file.read() ... sha256_hash = hashlib.sha256() ... sha256_hash.update(file_contents) ... computed_hash = sha256_hash.hexdigest()
Você pode adicionar uma instrução `assert` simples que verifica se o `computed_hash` é igual ao `original_hash`:
>>> assert computed_hash == original_hash
Se o arquivo foi modificado, como nesse caso, você receberá uma `AssertionError`:
Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
O hashing é útil para armazenar informações confidenciais, como senhas, em bancos de dados. Também pode ser usado na autenticação de senhas, comparando o hash da senha fornecida com o hash armazenado da senha correta.
Conclusão
Espero que este tutorial tenha esclarecido como gerar hashes seguros com Python. As principais conclusões são:
- O módulo `hashlib` do Python oferece implementações prontas de vários algoritmos de hash. A lista de algoritmos garantidos em sua plataforma pode ser obtida com `hashlib.algorithms_guaranteed`.
- Para criar um objeto hash, use o construtor genérico `new()` com a sintaxe: `hashlib.new(“nome-do-algoritmo”)`. Alternativamente, você pode usar os construtores correspondentes a algoritmos específicos, como: `hashlib.sha256()` para o hash SHA256.
- Após inicializar a mensagem e o objeto hash, invoque o método `update()` no objeto hash e, em seguida, o método `hexdigest()` para obter o hash.
- O hashing pode ser usado para verificar a integridade de software e arquivos, proteger dados confidenciais em bancos de dados e muito mais.
Em seguida, aprenda como criar um gerador de senhas aleatórias com Python.