Skip to content

Hashe a podpisy

Martin Skalicky edited this page Aug 5, 2026 · 1 revision

Hashe, HMAC a podpisy

Hash je nejpoužívanější kryptografický nástroj a zároveň ten, kolem kterého je nejvíc omylů. Není to šifrování, nedá se vrátit zpátky a sám o sobě nic nezabezpečuje. Přesto je uvnitř každého podpisu, každého certifikátu, každého Git commitu a každé uložené databáze hesel.

Tahle stránka vysvětluje, co hash umí, co neumí, a jak se z něj postaví ověření integrity, ukládání hesel a elektronický podpis.

Navazuje na stavební kameny a veřejný a soukromý klíč.

Co hash je

Funkce, která z libovolně velkého vstupu udělá otisk pevné délky:

echo -n "ahoj" | sha256sum
# 3f3b08eca62c21d76256e6e1d0b8bf99f4efbe376f64335b72f4163a8fc50dba

echo -n "ahoj." | sha256sum
# d16b42563ade4f7005d06bd65e1505a2ec3353f6b24acf5285b5c378101b2587

Jedna tečka navíc a otisk nemá s předchozím nic společného. Vstup může mít bajt nebo terabajt, výstup má vždycky 32 bajtů.

Vlastnosti, které od kryptografického hashe chceš:

Determinismus. Stejný vstup vždycky stejný výstup, na jakémkoliv stroji.

Odolnost proti nalezení vzoru. Z otisku nejde vypočítat vstup. Nejde to ani „s klíčem", protože žádný klíč neexistuje — informace se ztratila.

Odolnost proti druhému vzoru. Když máš konkrétní soubor, nikdo nedokáže vyrobit jiný soubor se stejným otiskem.

Odolnost proti kolizím. Nikdo nedokáže vyrobit libovolnou dvojici různých souborů se stejným otiskem. Tohle je nejtěžší požadavek a padá jako první.

Lavinový efekt. Změna jednoho bitu vstupu změní zhruba polovinu bitů výstupu.

Kolize existují vždycky

Vstupů je nekonečně mnoho, výstupů konečně. Dva různé soubory se stejným otiskem tedy nutně existují — jde jen o to, aby je nikdo nedokázal najít.

U 256bitového hashe je díky narozeninovému paradoxu očekávaný počet pokusů 2¹²⁸. To je stejná bariéra jako u AES-128, tedy fyzikálně nedosažitelná. Proto se říká, že SHA-256 poskytuje 128bitovou odolnost proti kolizím, i když má 256 bitů výstupu.

Které hashe používat

Funkce Stav Kde
SHA-256, SHA-512 výchozí volba TLS, certifikáty, balíčky, všude
SHA-512/256 SHA-512 zkrácené na 256 bitů rychlejší na 64bitech, imunní vůči prodloužení
SHA-3 / SHAKE v pořádku, jiná konstrukce pojistka, kdyby padla rodina SHA-2
BLAKE2, BLAKE3 v pořádku a velmi rychlé age, WireGuard, b3sum, zálohovací nástroje
SHA-1 prolomený jen jako identifikátor, ne jako ochrana
MD5 prolomený úplně leda kontrola překlepu v přenosu
CRC32 není kryptografický kontrola chyb linky, nic víc

Praktické doporučení: SHA-256, pokud nemáš důvod na něco jiného. BLAKE3, když počítáš otisky terabajtů a záleží ti na rychlosti.

Jak přesně padly MD5 a SHA-1

Stojí za to vědět, protože z toho plyne, co ještě smíš a co už ne.

MD5 — kolize se hledají od roku 2004 a dnes je to otázka sekund. V roce 2012 toho využil malware Flame: podvrhl si podpisový certifikát Microsoftu a tvářil se jako legitimní aktualizace Windows.

SHA-1 — v roce 2017 ukázal Google útok SHAttered: dva různé PDF soubory se stejným otiskem. V roce 2020 přišel útok s volitelnou předponou za zhruba 45 tisíc dolarů na pronajatém výpočetním výkonu, což je částka v dosahu kohokoliv, komu na tom záleží. To je nebezpečnější varianta — umožňuje vyrobit kolizi ke konkrétnímu smysluplnému dokumentu.

Co z toho plyne: kolize neznamená, že jde otisk zvrátit. Když má někdo tvůj SHA-1 otisk, pořád z něj tvůj soubor nedostane. Ale nemůže už platit tvrzení „shodný otisk = shodný soubor", a přesně to podpisy potřebují.

Proto Git, který SHA-1 používá jako identifikátor commitů, není „prolomený" — jen ho nemůžeš použít jako důkaz, že historii nikdo nezměnil. Na to je podpis commitů, případně repozitáře se SHA-256.

Past: prodloužení zprávy

Tohle je vlastnost, která překvapí i zkušené lidi, a stojí za ní nejběžnější domácí implementace ověřování API.

SHA-256 (a SHA-1, a MD5) staví na konstrukci Merkle–Damgård: data se zpracovávají po blocích a výstupem je vnitřní stav funkce. Když tedy znáš otisk, znáš i stav, ve kterém funkce skončila — a můžeš od něj pokračovat.

Důsledek: kdo zná SHA256(tajemství ‖ zpráva) a délku tajemství, umí spočítat platný otisk pro zpráva ‖ cokoliv, aniž by to tajemství znal.

mám:     SHA256(klíč ‖ "amount=10")          ← podepsané, důvěryhodné
vyrobím: SHA256(klíč ‖ "amount=10" ‖ pad ‖ "&amount=10000")   ← taky platné

Tomuhle se říká length extension attack a je to důvod, proč vlastní podepisování ve tvaru hash(tajemství + data) je chyba. Řešením je HMAC.

Imunní jsou SHA-512/256, SHA-3, BLAKE2 a BLAKE3 — ty vnitřní stav nezveřejňují.

HMAC

Standardní způsob, jak z hashe udělat ověření s klíčem. Není to hash s klíčem přilepeným zepředu, je to dvojité hashování s dvěma odvozenými klíči:

HMAC(K, m) = H( (K ⊕ opad) ‖ H( (K ⊕ ipad) ‖ m ) )

Ta vnější vrstva zabrání prodloužení zprávy, protože útočník nevidí stav vnitřního hashe.

# podepsat
echo -n "amount=10" | openssl dgst -sha256 -hmac "tajny-klic"

# v Pythonu, včetně bezpečného porovnání
python3 - <<'EOF'
import hmac, hashlib
sig = hmac.new(b"tajny-klic", b"amount=10", hashlib.sha256).hexdigest()
print(sig, hmac.compare_digest(sig, sig))
EOF

Porovnávej v konstantním čase. == skončí na prvním odlišném bajtu a rozdíl v době odpovědi prozradí, kolik znaků sedí. Útočník pak nehádá celý podpis, ale bajt po bajtu. Na to jsou hmac.compare_digest, subtle.ConstantTimeCompare, sodium_memcmp.

Kde HMAC potkáš: webhooky (GitHub, Stripe), JWT s algoritmem HS256, podepsané URL, ověřování relací, starší TLS 1.2, uvnitř HKDF.

Rozdíl proti podpisu je zásadní: HMAC ověří jen ten, kdo zná klíč — a ten ho zároveň umí vyrobit. Nedokazuje tedy, která ze dvou stran zprávu vytvořila. To umí jen podpis.

Hesla: úplně jiná disciplína

Na hesla se nikdy nepoužívá běžný hash. Ani SHA-256, ani BLAKE3, ani HMAC.

Důvod je rychlost. Grafická karta spočítá miliardy SHA-256 za sekundu. Když ti unikne databáze, útočník projede všechna hesla ze slovníku i všechny krátké kombinace během chvíle. To, co je u ověřování integrity výhoda, je tady fatální.

Potřebuješ funkci, která je úmyslně pomalá a paměťově náročná:

Funkce Doporučené nastavení Poznámka
Argon2id 19 MiB paměti, 2 průchody, 1 vlákno dnešní doporučení, odolná proti GPU i ASIC
scrypt N=2¹⁷, r=8, p=1 starší, ale paměťově náročná
bcrypt cost 12 omezení 72 bajtů, delší heslo se uřízne
PBKDF2-HMAC-SHA256 600 000 iterací jen když to vyžaduje certifikace; na GPU se počítá dobře

Sůl. Ke každému heslu náhodná hodnota, uložená vedle otisku. Bez ní má stejné heslo stejný otisk, takže útočník rozezná uživatele se stejným heslem a hlavně může použít předpočítanou tabulku pro všechny naráz. Sůl není tajná, jen unikátní. Všechny čtyři funkce výše ji řeší samy.

Pepř. Tajná hodnota mimo databázi, třeba v proměnné prostředí nebo v HSM. Přimíchá se ke každému heslu. Únik samotné databáze pak k hádání nestačí. Volitelné, ale levné.

Praktické: tohle si nepiš sám. Použij, co má framework — password_hash() v PHP, argon2-cffi v Pythonu, bcrypt v Node. Ve svém homelabu tuhle úlohu nejspíš vůbec nemáš, protože přihlašování řeší Pocket ID nebo Authelia.

Ještě lepší je hesla neukládat

Nejbezpečnější databáze hesel je ta, kterou nemáš. Passkey (WebAuthn) nahrazuje heslo dvojicí klíčů: server má veřejný, tvoje zařízení soukromý. Nedá se uhodnout, nedá se odposlechnout a nedá se vyfishovat, protože prohlížeč podepíše i doménu, na které jsi. Podvržený web dostane podpis, který jeho vlastní adresu odmítá.

KDF: odvození klíčů

Nezaměňuj s hashováním hesel, i když to vypadá podobně.

HKDF — pro vstupy, které už jsou náhodné (výsledek Diffie-Hellmana, obsah /dev/urandom). Nemá důvod být pomalá. Skládá se ze dvou kroků: extract z nerovnoměrného vstupu vytáhne rovnoměrný klíč, expand z něj vyrobí libovolný počet pojmenovaných klíčů.

klíč_pro_klienta = HKDF(tajemství, "client traffic")
klíč_pro_server  = HKDF(tajemství, "server traffic")

Ten popisek je důležitý: díky němu jsou oba klíče různé, přestože vznikly ze stejného tajemství. Kdyby byly stejné, šlo by pakety přehrávat zpátky odesílateli. Přesně takhle staví klíče TLS 1.3 i WireGuard.

Argon2id a spol. — pro hesla. Musí být pomalá, protože vstup je předvídatelný.

Chyba, která se dělá: použít HKDF na heslo. Pak je odvozený klíč tak silný jako to heslo, což je málo.

Digitální podpis

Kombinace hashe a asymetrické kryptografie.

podepsání:  otisk = H(dokument)  →  podpis = Sign(soukromý_klíč, otisk)
ověření:    otisk = H(dokument)  →  Verify(veřejný_klíč, otisk, podpis)

Podepisuje se otisk, ne dokument. Ze dvou důvodů: asymetrické operace jsou pomalé a u RSA se do klíče vejde jen pár set bajtů. Zároveň z toho plyne důležitá věc — podpis není silnější než použitý hash. Podpis nad SHA-1 je dnes bezcenný bez ohledu na sílu klíče, protože stačí vyrobit jiný dokument se stejným otiskem.

Schémata

Ed25519 — dnešní volba. Podpis 64 bajtů, klíč 32 bajtů, deterministické (nepotřebuje při podepisování náhodnost), rychlé, bez okrajových případů.

ECDSA (P-256, P-384) — rozšířené v certifikátech. Funguje, ale vyžaduje pro každý podpis unikátní náhodné číslo a jeho zopakování znamená okamžitou ztrátu soukromého klíče. Viz náhodnost a entropie.

RSA-PSS — moderní zarovnání pro RSA, s důkazem bezpečnosti. Používej, kde to protistrana umí.

RSA PKCS#1 v1.5 — staré zarovnání. U podpisů zatím drží, u šifrování je zdroj opakovaných útoků. Nové věci na něm nestav.

DSA — mrtvé. OpenSSH ho odstranil úplně.

Co podpis dokazuje a co ne

Dokazuje: dokument vytvořil někdo, kdo má soukromý klíč, a od té doby se nezměnil ani o bit.

Nedokazuje: že ten někdo je ten, koho máš na mysli. Tohle je vazba, kterou musí řešit něco dalšího — certifikát od CA, zápis v known_hosts, klíč zadaný do konfigurace, osobní předání.

Nedokazuje ani: kdy vznikl. Systémový čas se dá přenastavit. Na to jsou časová razítka od nezávislé autority.

Kontrolní součet není podpis

Klasická chyba u stahování softwaru:

software.tar.gz         ← stažené
software.tar.gz.sha256  ← z téhož serveru

Kdo podstrčil první soubor, podstrčil i druhý. Otisk ze stejného zdroje jako data nedokazuje nic kromě toho, že se přenos nepokazil.

Smysl to dává ve dvou případech: otisk je na jiném místě (README v gitu, jiná doména), nebo je k souboru podpis ověřitelný veřejným klíčem, který už máš z dřívějška.

# tohle ověřuje jen přenos
sha256sum -c software.tar.gz.sha256

# tohle ověřuje původ
gpg --verify software.tar.gz.asc software.tar.gz
ssh-keygen -Y verify -f allowed_signers -I autor@example.cz -n file \
    -s software.tar.gz.sig < software.tar.gz

Kde v síti narazíš na hash

Místo Co to dělá
Certifikát CA podepisuje otisk certifikátu, sha256WithRSAEncryption
TLS handshake otisk celého průběhu se podepisuje → ochrana proti podvržení
SSH host key otisk v known_hosts, SHA256:... při prvním připojení
APT / DNF podpis seznamu balíčků, otisk každého balíčku
Git identifikátor commitu, podpis commitu
Docker sha256:... u obrazu — jediný spolehlivý identifikátor, na rozdíl od značky
HSTS preload, CT logy Merkleovy stromy nad otisky
DNSSEC, DANE, SSHFP otisky klíčů v DNS
Fail2ban, CrowdSec ne, tam žádný hash není — jen ať to nehledáš

Ten řádek s Dockerem stojí za zdůraznění. nginx:latest ukazuje pokaždé na něco jiného. nginx@sha256:abc... ukazuje navždy na tentýž obsah — a přesně tohle chceš v produkci a v docker-compose.yml, který má fungovat i za rok.

Praktické příkazy

# otisk souboru
sha256sum soubor
openssl dgst -sha256 soubor
b3sum soubor                       # BLAKE3, výrazně rychlejší na velkých datech

# otisk řetězce — pozor na -n, jinak počítáš i odřádkování
echo -n "text" | sha256sum

# ověření stažených souborů podle seznamu
sha256sum -c SHA256SUMS 2>&1 | grep -v OK

# otisk veřejného klíče v certifikátu (pro pinning)
openssl x509 -in cert.pem -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary | base64

# otisk SSH klíče
ssh-keygen -lf ~/.ssh/id_ed25519.pub
ssh-keyscan -t ed25519 server 2>/dev/null | ssh-keygen -lf -

Časté omyly

„Hash se dá dešifrovat." Nedá. Stránky, které to nabízejí, mají tabulku předpočítaných otisků běžných hesel. Proto se solí.

„Dvojitý hash je bezpečnější." sha256(sha256(x)) nepřidá prakticky nic a u hesel to situaci nezachrání — pořád je to miliarda pokusů za sekundu.

„MD5 stačí, když jde jen o kontrolu." Na kontrolu chyby v přenosu ano. Jakmile v úvaze figuruje útočník, ne.

„Máme hesla hashovaná, takže je únik databáze v pohodě." Záleží čím. SHA-256 bez soli je prakticky totéž jako otevřený text. Argon2id s rozumným nastavením je skutečná ochrana.

„Sůl musí být tajná." Nemusí, ukládá se vedle otisku. Tajný je pepř, a ten je nepovinný navíc.

„Podepsané = důvěryhodné." Podepsané znamená „vím, kdo to vyrobil". Jestli je to ten správný někdo, je jiná otázka. Podepsaný malware je pořád malware.

Kam dál

Clone this wiki locally