Hur lagrar man användarnas lösenord säkert?

Det här inlägget publicerades ursprungligen på bloggen Entreprenörd 9 mars 2009.

Det skrivs ibland om webbtjänster som blir hackade och lösenord som läcker ut. Då är det en fördel om lösenorden är skyddade på något sätt, men så är det inte alltid. Hur kan man då göra för att säkra lösenorden för den händelse att ens tjänst skulle bli hackad? Jag har definierat tre säkerhetsnivåer nedan.

Nivå ”usel”: Klartext

Jag tror att de flesta webbtjänster lagrar användarnas lösenord i klartext, möjligen krypterat med en nyckel eller chiffrerat med en egen funktion eller ROT13. Alla tjänster som kan skicka ut bortglömda lösenord faller i den här kategorin. Det är inte bra, eftersom alla lösenord är tillgängliga för hackaren.

(Använder man en nyckel för tvåvägskryptering kan en hackare förstås hitta den nyckeln också, och då blir samtliga lösenord helt plötsligt läsbara i klartext.)

Nivå ”ok”: Hash

Ett snäpp bättre är att köra lösenorden genom en envägskryptering (kallas hashfunktion) och bara lagra resultatet. Populära funktioner är MD5 och SHA-1. När en användare loggar in, hashar man det angivna lösenordet och jämför med hashen i databasen för att verifiera.

Teorin bakom en hashfunktion är att det ska vara omöjligt att härleda lösenordet utifrån hashen. Även om någon kommer över en databas full med hashar, känner man inte till ett enda lösenord. För att knäcka en hash krävs det att man genererar hashar för alla tänkbara lösenord och ser vilka som matchar. Det tar lång tid, vilket är resonemanget bakom all kryptering.

Men eftersom en hashfunktion inte tar en nyckel som argument, får alla webbtjänster i hela världen alltid samma hash från samma lösenord (om man använder samma funktion, och jag tror att de flesta gör det). Det har gjort att hackare har investerat tid i att skapa uppslagstabeller för hashar. De kör helt enkelt alla tänkbara strängar genom en hashfunktion, och helt plötsligt kan de översätta ”9726255eec083aa56dc0449a21b33190” till ”money” och så har de knäckt alla svaga lösenord. Läs mer om så kallade rainbow tables på Wikipedia.

(Starka lösenord är dock relativt svårt att komma åt om de är hashade. Det finns betydligt fler starka än svaga lösenord, så hackarna har kanske inte hunnit generera hashar för alla starka lösenord.)

Du bör förresten undvika funktionen MD5, eftersom det finns kända sårbarheter i den. Om du skapar en ny tjänst ser jag ingen anledning till att inte använda SHA-256 eller SHA-512, som är nyare och starkare varianter av SHA-1. (SHA-algoritmerna är framtagna av amerikanernas motsvarighet till FRA: NSA.)

Nivå ”utmärkt”: Saltat hash

För att göra det omöjligt att använda uppslagstabeller för hasharna, kan man enkelt lägga till ett ”salt” till lösenordet innan man kör det genom hash-funktionen. När man sedan ska verifiera inloggning, använder man samma salt och jämför resultaten. (Ett salt är vanligtvis en sträng.)

Hashen blir då beroende av saltet, och uppslagstabellerna måste göras om för varje salt. För att förtydliga, visar jag här en hash med MD5 med olika salt:

Lösenord Salt Hash
money inget 9726255eec083aa56dc0449a21b33190
money salt 36953a1b0dc73ebbcc992497d6bb07bc
money NaCl 45337febad40d0d3fb4571771f07b034

Som du ser blir hasharna helt olika. Du bör därför använda ett unikt salt för att dina hashar ska vara unika och svårare att knäcka.

Det bästa är att skapa ett slumpmässigt salt för varje användare, och lagra detta i databasen. (Nej, du behöver inte kryptera saltet.) Det är inte lika bra att använda samma salt för alla användare, men i alla fall bättre än att inte salta alls.

Uppdaterat: Observera att saltet måste vara slumpmässigt och av en viss längd. Om du använder salt som bara är ett par tecken långt, är det fortfarande ganska enkelt att knäcka med rainbow tables. Saltet bör vara minst 15 tecken långt enligt Dilbert-principen ”I didn’t have any accurate numbers, so I made up this one”. 😉

Källkod (PHP)

Så, hur gör man nu? För att hjälpa dig på traven ska du få se min egen källkod, som jag använder i ett projekt. Där skapar jag ett slumpmässigt salt (av slumpmässig längd) för varje ny användare och lagrar det i en kolumn i användar-tabellen. Jag har dessutom ett statiskt salt, som är slumpmässigt genererat en gång för alla. Jag använder hash-funktionen SHA-256, som finns i modulen mhash för PHP (som tyvärr inte är installerad som standard).

För att skapa ett slumpmässigt salt:

function makeRandomString($minLength, $maxLength)
{
  $length = mt_rand($minLength, $maxLength);
  $string = "";
  for($i = 0; $i < $length; $i++)
    $string .= chr(mt_rand(32, 126));
  return $string;
}

Och för att hasha lösenordet:

function hashPassword($password, $salt = "")
{
  return base64_encode(mhash(MHASH_SHA256,
    $salt.$password.'HJ"UPHNtfU`__HEtX;#G@4'));
}

Strängen som står efter $password är alltså det statiska saltet, och du bör generera ett eget, eller möjligen strunta i det statiska saltet. Jag är osäker på vad det egentligen tillför i säkerhet.

Om du inte har tillgång till modulen mhash, kan du istället använda den inbyggda funktionen sha1 på samma sätt:

return sha1($salt.$password.'HJ"UPHNtfU`__HEtX;#G@4');

Du kanske också har stöd för funktionen hash, då kan du skriva:

return hash('sha256', $salt.$password.'HJ"UPHNtfU`__HEtX;#G@4');

Läs också de artiklar som inspirerade denna:

Uppdatering: Läs denna avancerade artikel för ännu mer information: Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes. Han ondgör sig bland annat över SHA-algoritmerna, som är för snabba. Ju snabbare algoritm, desto snabbare går det ju att knäcka.

Uppdatering: Du bör också läsa min uppföljare till den här artikeln.

En reaktion på ”Hur lagrar man användarnas lösenord säkert?”

  1. Hej,

    Det du beskriver är en bra sak att tänka på. Men du kan titta på TNO ( Trust No One ) och PIE ( Pre Internet Encryption ) för att utveckla säkrare autentisiering av användaren beroende på vilken site och applikation man bygger. Titta även på TOTP ( Timebased One Time Passcode ) så som Google authenticator. Yubikey från yubico är också en sak man bör titta på för en säkrare autentisiering.

    Med vänlig hälsning, Samuel Lundmark

Kommentarer inaktiverade.