Udělat validaci hesel správně, to je kumšt. Obvyklá pravidla - že heslo musí obsahovat velké písmeno, malé písmeno, číslici, sumerské klínové písmo a krev jednorožce - jsou obvykle spíše k vzteku než k užitku. Pokud jde o pravidla pro hesla, doporučuji používat následující:
- Minimální délka hesla (
RequiredLength
) dvanáct znaků. S krátkým heslem dneska prostě parádu neuděláte a dvanáct znaků pokládám za rozumný kompromis. - Minimální počet různých znaků (
RequiredUniqueChars
) čtyři. Heslo by mělo mít nějakou entropii a dvacet znakůX
není úplně to pravé. - Nemělo by být odvozeno od informací o uživateli (e-mail, uživatelské jméno).
- Nemělo by být příliš běžné, nápadné.
O body 1 a 2 se dokáže postarat vestavěný validátor hesla. Stačí ho nastavit nějak takto:
services.AddIdentity<ApplicationUser, ApplicationRole>(options => {
options.Password.RequiredLength = 12;
options.Password.RequiredUniqueChars = 4;
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
S těmi dalšími body nám ovšem vestavěný validátor nepomůže. Pokud se týče informací o uživateli, tam obecné řešení napsat nelze. Záleží na tom, jaké informace máme k dispozici. Andrew Lock napsal článek, ve kterém vytváří validátor, který kontroluje, že se heslo nerovná uživatelskému jménu. Osobně bych zašel ještě dál, kontroloval bych, zda heslo neobsahuje uživatelské jméno, podstatné části e-mailové adresy, nebo třeba datum narození či telefonní číslo, pokud je u uživatele známe. Nicméně zde musíte napsat řešení na míru pro váš vlastní systém.
Kontrola příliš často užívaných hesel
Další řešení od stejného autora kontroluje, zda se heslo nenachází v seznamu nejběžnějších. Vychází přitom z databáze deseti miliónů nejčastějších hesel z roku 2016. Součástí jeho knihovny jsou validátory, které zabrání použití hesla, které se nachází v top 100, 500, 1000, 10000 a 100000.
Zabránit použití takových hesel je dobré především kvůli brute force útokům. Stejnou databází disponuje i útočník, který v případě úniku (i kvalitně zahashovaných) hesel samozřejmě nejdřív vyzkouší ta nejběžnější.
Kontrola přes Have I Been Pwned
Trou Hunt provozuje službu Have I Been Pwned, která umožňuje hlídat, zda se vaše přihlašovací údaje neobjevily v nějakém z úniků. Jako přidruženou výrobu nabízí Pwned Passwords API. Tedy službu, která umožní ověřit si, že se potenciální heslo nenachází mezi běžně používanými a uniklými z mnoha zdrojů.
Kontrola je přitom udělána tak, že se službě nezasílá heslo ani jeho hash. Využívá přitom k-anonymity, která v tomto případě funguje následovně.
- Z potenciálního hesla se spočítá SHA-1 hash a převede se do Base16. Tento hashovací algoritmus není dostatečně bezpečný pro úschovu hesel a většinu dalších užití, ale je dostatečně dobrý pro to naše.
- Pošle se HTTPS GET požadavek na adresu
https://api.pwnedpasswords.com/range/12345
, kde je místo12345
prvních pět znaků spočítaného hashe. Služba tedy neví, jaké heslo uživatel zvolil, ani jeho hash, jenom prvních pět číslic z jeho šestnáctkového zápisu. - Služba vrátí seznam hashů všech známých kompromitovaných hesel, jejichž hash začíná uvedenými pěti číslicemi.
- Klient (náš validátor) se podívá, zda se v seznamu nachází zbytek hashe. Pokud ano, bylo heslo v minulosti kompromitováno. Pokud ne, je v pořádku.
Projekt PwnedPasswordsValidator
Napsal jsem validátor, který využívá výše zmíněné API. A aby to bylo zábavnější, tak jsem to celé živě streamoval na YouTube. Zde je záznam streamu:
Hotový validátor je k dispozici na GitHubu a jako NuGet balíček Altairis.Services.PwnedPasswordsValidator
.
Změny proti streamu
Po dokončení streamu jsem provedl ještě některá drobná vylepšení, kterými jsem nechtěl stream prodlužovat:
- Využívám asynchronní
System.Net.Http.HttpClient
místo staréhoSystem.Net.WebClient
. - Přidal jsem konfigurovatelný timeout.
- Přidal jsem logování a obsluhu chyb. Pokud by bylo API nedostupné (nebo včas neodpoví), aplikace heslo "propustí" a zaloguje chybu.
- Trochu jsem zefektivnil prohledávání vrácené odpovědi. Šlo by to napsat ještě lépe (s využitím vědomí, že hashe jsou v odpovědi seřazené), ale to už je úroveň optimalizace, která mi přijde zbytečná.