Bezpečné hašování pomocí Pythonu Hashlib

Tento návod vás provede procesem vytváření bezpečných hashů s využitím vestavěných funkcí modulu hashlib v Pythonu.

Znalost významu hashování a schopnost programově vypočítat bezpečné hashovací hodnoty může být přínosná, i když se nevěnujete vývoji zabezpečení aplikací. Proč tomu tak je?

Při práci na projektech v Pythonu se pravděpodobně setkáte s nutností ukládat hesla a další citlivá data do databází nebo souborů se zdrojovým kódem. V takových situacích je bezpečnější aplikovat hashovací algoritmus na tyto citlivé informace a ukládat do úložiště hash namísto samotných dat.

V tomto průvodci se zaměříme na to, co hashování vlastně znamená a jak se liší od šifrování. Prozkoumáme také charakteristiky bezpečných hashovacích funkcí. Následně využijeme běžné hashovací algoritmy k výpočtu hashů prostého textu v Pythonu, a to pomocí vestavěného modulu hashlib.

Pojďme se do toho pustit!

Co je to hashování?

Hashování je proces, kdy se vezme vstupní zpráva (libovolný řetězec) a vytvoří se z ní výstup o pevné délce, který se nazývá hash. To znamená, že délka výstupního hashe je pro daný hashovací algoritmus vždy stejná, bez ohledu na délku vstupních dat. Ale jak se hashování liší od šifrování?

Při šifrování je vstupní zpráva zašifrována pomocí šifrovacího algoritmu, čímž vznikne šifrovaný výstup. Na tento zašifrovaný výstup je pak možné aplikovat dešifrovací algoritmus a získat zpět původní zprávu.

Hashování ovšem funguje jinak. Jak už jsme zmínili, šifrování je obousměrný proces – lze přejít od zašifrované zprávy k té nezašifrované a naopak.

Na rozdíl od šifrování je hashování jednosměrný proces. To znamená, že není možné získat původní vstupní zprávu z vypočítaného hashe.

Vlastnosti hashovacích funkcí

Pojďme si rychle projít vlastnosti, které by měla správná hashovací funkce splňovat:

  • Deterministická: Hashovací funkce jsou deterministické. To znamená, že pro stejný vstup m je výsledný hash vždy stejný.
  • Odolná vůči předobrazu (Preimage Resistant): Tato vlastnost znamená, že je prakticky nemožné najít vstupní zprávu m z vypočteného hashe. Je to proto, že hashování je jednosměrný proces.
  • Odolná vůči kolizím: Mělo by být velmi obtížné (nebo výpočetně nemožné) najít dva různé vstupní řetězce zpráv m1 a m2 tak, aby hash(m1) byl stejný jako hash(m2).
  • Odolná vůči druhému předobrazu (Second Preimage Resistant): Pokud už máme vstupní zprávu m1 a její hash, mělo by být nemožné najít jinou zprávu m2, která by měla stejný hash jako m1.

Modul hashlib v Pythonu

Vestavěný modul hashlib v Pythonu nabízí implementace mnoha algoritmů pro hashování a zpracování zpráv, včetně algoritmů SHA a MD5.

Pro využití funkcí a konstruktorů z modulu hashlib jej musíte importovat do svého prostředí:

import hashlib

Modul hashlib definuje konstanty `algorithms_available` a `algorithms_guaranteed`, které obsahují sady dostupných a garantovaných hashovacích algoritmů pro danou platformu.

Z logiky vyplývá, že `algorithms_guaranteed` je podmnožinou `algorithms_available`.

Spusťte Python REPL, importujte `hashlib` a prozkoumejte konstanty `algorithms_available` a `algorithms_guaranteed`:

>>> hashlib.algorithms_available
# Výstup
{'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
# Výstup
{'md5', 'shake_256', 'sha3_256', 'shake_128', 'blake2b', 'sha3_224', 'sha3_384',
'sha384', 'sha256', 'sha1', 'sha3_512', 'sha512', 'blake2s', 'sha224'}

Je vidět, že `algorithms_guaranteed` je skutečně podmnožinou `algorithms_available`.

Jak vytvořit hash objekty v Pythonu

Nyní se naučíme, jak vytvářet hash objekty v Pythonu. Ukážeme si výpočet SHA256 hashe řetězce zprávy pomocí dvou metod:

  • Obecného konstruktoru `new()`
  • Konstruktorů specifických pro algoritmus

Použití konstruktoru `new()`

Začneme inicializací řetězce zprávy:

>>> message = "etechblog.cz je super!"

Pro vytvoření instance hash objektu použijeme konstruktor `new()` a předáme název algoritmu:

>>> sha256_hash = hashlib.new("SHA256")

Nyní můžeme zavolat metodu `update()` na objekt hash s řetězcem zprávy jako argumentem:

>>> sha256_hash.update(message)

Při pokusu o provedení tohoto kroku dojde k chybě, protože hashovací algoritmy pracují pouze s bajtovými řetězci.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Unicode-objects must be encoded before hashing

Pro zakódování řetězce do bajtů použijte metodu `encode()` a poté ji použijte ve volání metody `update()`. Nakonec použijte metodu `hexdigest()` k získání SHA256 hashe odpovídajícího zprávě.

sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Výstup:'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Místo kódování řetězce pomocí `encode()` můžete řetězec definovat jako bajtový řetězec přidáním písmene `b` před řetězec:

message = b"etechblog.cz je super!"
sha256_hash.update(message)
sha256_hash.hexdigest()
# Výstup: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Výsledný hash je stejný jako v předchozím případě, což dokazuje deterministickou povahu hashovacích funkcí.

Navíc, i malá změna v řetězci zprávy by měla vést k drastické změně hashe (tzv. „lavinový efekt“).

Pro ověření změníme ‚s‘ v ‚super‘ na ‚S‘ a spočítáme nový hash:

message = "etechblog.cz je Super!"
h1 = hashlib.new("SHA256")
h1.update(message.encode())
h1.hexdigest()
# Výstup: '8e6051d024c0806c95281640ef65047769a4f8b42128578329673c95f960d652'

Je vidět, že se hash dramaticky změnil.

Použití konstruktoru specifického pro algoritmus

V předchozím příkladu jsme použili obecný konstruktor `new()` s argumentem „SHA256“ pro vytvoření hash objektu.

Alternativně můžeme použít konstruktor `sha256()`, jak je ukázáno níže:

sha256_hash = hashlib.sha256()
message= "etechblog.cz je super!"
sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Výstup: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Výsledný hash je stejný jako hash, který jsme získali dříve pro zprávu „etechblog.cz je super!“.

Prozkoumání atributů hash objektů

Hash objekty mají několik užitečných atributů:

  • Atribut `digest_size` udává velikost hashe v bajtech. Například algoritmus SHA256 vrací 256bitový hash, což je ekvivalent 32 bajtů.
  • Atribut `block_size` odkazuje na velikost bloku používaného v hashovacím algoritmu.
  • Atribut `name` je název algoritmu, který lze použít v konstruktoru `new()`. Prozkoumání tohoto atributu je užitečné, pokud hash objekty nemají popisné názvy.

Můžeme prozkoumat tyto atributy pro objekt `sha256_hash`, který jsme vytvořili dříve:

>>> sha256_hash.digest_size
32
>>> sha256_hash.block_size
64
>>> sha256_hash.name
'sha256'

Podívejme se na některé praktické aplikace hashování pomocí modulu hashlib v Pythonu.

Praktické příklady hashování

Ověřování integrity softwaru a souborů

Jako vývojáři neustále stahujeme a instalujeme softwarové balíčky. Nezáleží na tom, zda pracujeme v Linuxu, Windows nebo macOS.

Některá zrcadla softwarových balíčků ale nemusí být důvěryhodná. Proto najdeme u odkazu ke stažení i hash (nebo kontrolní součet). Integritu staženého softwaru ověříme výpočtem hashe a porovnáním s oficiálním hashem.

Stejný princip lze použít i u souborů v našem počítači. I drobná změna v obsahu souboru dramaticky změní hash. Tak můžeme kontrolovat, zda soubor nebyl pozměněn, ověřením jeho hashe.

Zde je jednoduchý příklad. Vytvořte textový soubor `muj_soubor.txt` v pracovním adresáři a přidejte do něj nějaký obsah:

$ cat muj_soubor.txt
Toto je vzorový textový soubor.
Spočítáme SHA256 hash tohoto textového souboru a také
zkontrolujeme, zda byl soubor modifikován
přepočítáním hashe.

Nyní můžete soubor otevřít v binárním režimu čtení (`rb`), přečíst jeho obsah a vypočítat SHA256 hash:

>>> import hashlib
>>> with open("muj_soubor.txt","rb") as file:
...     file_contents = file.read()
...     sha256_hash = hashlib.sha256()
...     sha256_hash.update(file_contents)
...     original_hash = sha256_hash.hexdigest()

Proměnná `original_hash` teď obsahuje hash souboru `muj_soubor.txt` v jeho aktuálním stavu.

>>> original_hash
# Výstup: 'f411b91f9e0a41b32e8137664b265b7e579047a20a3b1060b94e00e5c65a5956'

Nyní upravte soubor `muj_soubor.txt`. Například odeberte přebytečné mezery na začátku řádku se slovem „Spočítáme“.

Opět spočítejte hash a uložte ho do proměnné `computed_hash`.

>>> import hashlib
>>> with open("muj_soubor.txt","rb") as file:
...     file_contents = file.read()
...     sha256_hash = hashlib.sha256()
...     sha256_hash.update(file_contents)
...     computed_hash = sha256_hash.hexdigest()

Nyní přidáme jednoduchý `assert` příkaz, který ověří, zda se `computed_hash` rovná `original_hash`.

>>> assert computed_hash == original_hash

Pokud byl soubor změněn (což je v tomto případě pravda), dojde k chybě `AssertionError`:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Hashování můžete použít i při ukládání citlivých informací, jako jsou hesla v databázích. Během připojování k databázi pak můžete hash využít pro ověření hesla. Ověřte hash zadaného hesla proti hashi správného hesla.

Závěr

Doufám, že vám tento tutoriál pomohl pochopit, jak generovat bezpečné hashe v Pythonu. Zde jsou hlavní poznatky:

  • Modul `hashlib` v Pythonu nabízí implementace několika hashovacích algoritmů připravených k okamžitému použití. Seznam garantovaných algoritmů pro vaši platformu získáte pomocí `hashlib.algorithms_guaranteed`.
  • Pro vytvoření hash objektu použijte obecný konstruktor `new()` se syntaxí `hashlib.new(„nazev-algoritmu“)`. Alternativně můžete použít konstruktory specifické pro hashovací algoritmy, například: `hashlib.sha256()` pro SHA 256 hash.
  • Po inicializaci řetězce, který má být hashován, a vytvoření hash objektu, použijte metodu `update()` na hash objekt a následně metodu `hexdigest()` k získání hashe.
  • Hashování je užitečné při kontrole integrity softwaru a souborů, ukládání citlivých informací v databázích a dalších situacích.

V dalším kroku se podíváme, jak naprogramovat generátor náhodných hesel v Pythonu.