Programování

Jak bezpečně ukládat hesla: Argon2id, bcrypt a co nepoužívat

Proč MD5 ani SHA-256 na hesla nestačí, jak funguje sůl a pomalé hashovací funkce a jak správně hashovat hesla v PHP, Pythonu a Node.js.

12. 10. 2026 · 4 min čtení

Když uniknou data z databáze, rozhoduje o škodě jediná věc: jak byla uložena hesla. Správně zahashovaná hesla útočník prakticky nezíská, špatně uložená prolomí během hodin. Dobrá zpráva je, že správné řešení zabere pár řádků kódu.

Co nedělat

  • Hesla v čitelné podobě. Bez komentáře.
  • Šifrování (AES apod.). Šifra je vratná – kdo získá klíč, má všechna hesla. Heslo nikdy nepotřebujete dešifrovat, jen ověřit.
  • MD5, SHA-1, SHA-256, SHA-512. Jsou to kryptografické hashe, ale navržené pro rychlost. Moderní GPU jich spočítá miliardy za sekundu, takže běžná hesla padnou útokem hrubou silou nebo slovníkem.
  • Vlastní konstrukce typu sha256(salt + heslo) opakované tisíckrát. Pravděpodobně v tom bude chyba a stejně to bude slabší než hotové řešení.

Co dělá dobrý hash hesla

  1. Sůl – náhodná hodnota unikátní pro každé heslo. Dva uživatelé se stejným heslem mají jiný hash a předpočítané tabulky (rainbow tables) jsou k ničemu.
  2. Pomalost – nastavitelná cena výpočtu, aby jeden pokus trval desítky až stovky milisekund.
  3. Paměťová náročnost (u Argon2 a scrypt) – výrazně ztěžuje útok na GPU a specializovaném hardwaru.

Moderní knihovny sůl generují samy a ukládají ji přímo do výsledného řetězce spolu s parametry. Do databáze tedy stačí jeden sloupec.

Které algoritmy použít

Algoritmus Doporučení
Argon2id první volba pro nové projekty
bcrypt stále bezpečný, široce podporovaný; pozor na limit 72 bajtů vstupu
scrypt dobrý, pokud Argon2 není dostupný
PBKDF2 jen kde je vyžadována certifikace FIPS, s vysokým počtem iterací

OWASP Password Storage Cheat Sheet doporučuje pro Argon2id minimálně 19 MiB paměti, 2 iterace a paralelismus 1. Výchozí hodnoty knihoven bývají podobné nebo vyšší.

PHP

PHP má vše vestavěné od verze 5.5 (bcrypt) a 7.3 (Argon2id, pokud je PHP zkompilované s libargon2):

<?php
$hash = password_hash($password, PASSWORD_ARGON2ID);
// nebo: password_hash($password, PASSWORD_DEFAULT); // aktuálně bcrypt

if (password_verify($inputPassword, $hash)) {
    if (password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
        $newHash = password_hash($inputPassword, PASSWORD_ARGON2ID);
        // uložit $newHash do databáze
    }
    // přihlášení OK
}

password_needs_rehash umožní plynule přejít na silnější parametry – hash se přepočítá při dalším přihlášení. Sloupec dimenzujte s rezervou, například VARCHAR(255).

Python

S knihovnou argon2-cffi (pip install argon2-cffi):

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher()  # rozumné výchozí parametry

hash_ = ph.hash(password)

try:
    ph.verify(hash_, input_password)
    if ph.check_needs_rehash(hash_):
        hash_ = ph.hash(input_password)  # a uložit
except VerifyMismatchError:
    print("Špatné heslo")

Django a FastAPI projekty často používají passlib nebo vestavěné hashery (Django má Argon2PasswordHasher, který stačí zapnout v PASSWORD_HASHERS).

Node.js

Balíček argon2 (npm install argon2):

import argon2 from "argon2";

const hash = await argon2.hash(password); // výchozí typ je argon2id

if (await argon2.verify(hash, inputPassword)) {
  // přihlášení OK
}

Pro bcrypt existuje bcrypt (nativní) nebo bcryptjs (čistý JavaScript, pomalejší). Cost faktor nastavte alespoň na 10, raději 12.

Jak nastavit cenu

Změřte na produkčním hardwaru, kolik trvá jeden hash. Cíl je zhruba 100–500 ms pro přihlášení – pro uživatele nepostřehnutelné, pro útočníka zdrcující. Myslete ale i na zátěž: pokud se přihlašuje sto lidí za sekundu, server to musí utáhnout. Přihlašovací endpoint proto vždy chraňte i rate limitingem, jinak se z pomalého hashe stává nástroj pro DoS.

Další dobré praktiky

  • Nevymýšlejte limity složitosti typu „velké písmeno a speciální znak“. Aktuální NIST SP 800-63B doporučuje spíš minimální délku (alespoň 8, lépe 12+ znaků), povolit dlouhá hesla a kontrolovat je proti seznamu uniklých hesel.
  • Kontrola uniklých hesel přes Have I Been Pwned Pwned Passwords API funguje anonymně: posíláte jen prvních 5 znaků SHA-1 hashe (k-anonymity).
  • Porovnání v konstantním čase zajišťují funkce verify samy; nehashujte a nesrovnávejte ručně přes ==.
  • Pepper – tajný klíč mimo databázi (například HMAC hesla před hashováním) – přidává ochranu, pokud unikne jen databáze. Zkomplikuje ale rotaci klíče.
  • Neomezujte délku hesla na 16 znaků. Rozumný horní limit (třeba 128 znaků) jen kvůli ochraně před DoS.
  • Logy – nikdy nelogujte těla přihlašovacích požadavků.

Migrace ze starého MD5

Nemusíte čekat, až se všichni přihlásí. Zabalte starý hash do nového: argon2id(md5_hash), označte záznam příznakem a při přihlášení ověřujte argon2id(md5(heslo)). Po úspěšném přihlášení hash nahraďte čistým argon2id(heslo). Tím ochráníte okamžitě všechny účty, i ty neaktivní.