Currently Browsing: PHP

Spara lösenord säkert med hash+salt

Det har varit väldigt mycket liv kring hur man ska spara lösenord den senaste tiden. Det är knappast någon som missat att Bloggtoppen blev hackat och att Aftonbladet lyfte fram detta som enorm händelse. Eftersom detta är högst aktuellt även om jag borde skrivit detta i förra veckan så tänkte jag passa på att gå in på säkerheten och hur du på bästa sätt kan spara användares lösenord i databasen. Mitt exempel kommer varit skrivet i PHP men samma princip gäller för vilket program/script-språk som helst.

Innan jag går in på min lösning så tänkte jag gå igenom säkerhetsproblemen som uppstår och varför de är dåliga. Jag är absolut ingen säkerhetsexpert på ämnet men jag känner ändå att jag har relativt bra koll på läget och en bra lösning på säkerhetsproblemen. Kanske läser någon med bra säkerhetskunskaper detta och kan ge feedback på min lösning

Säkerhetsproblemet med Bloggtoppen

Problemet med Bloggtoppen och hur de bevarade lösenorden var att de endast körde en s.k. MD5 hash på lösenorden och utan att använda sig av någon salt för att försvåra framtagandet av lösenorden. MD5 är en kryptografisk hashfunktion som är framtagen 1991 av Ronald Rivest. Den skapar en 128bit hashsträng av en valfri sträng och den gör det också väldigt snabbt. Och snabbt är i detta fallemer på ont än på gott.

Bloggtoppen använder sig av ett topplist-script vid namn evoTopsites om jag inte är helt ute och cyklar. Ett script som inte längre uppdateras eller har en officiell hemsida. Det är gammalt och likt många gamla script så använder det endast en md5-hash för att lagra lösenorden. Idag är en md5-hash utan salt knappt bättre än att lagra lösenorden i klartext. Kombinerat med ett bra salt-värde så är det helt okej även om det finns bättre lösningar.

Eftersom MD5 är så snabbt och eftersom det använts av så många gamla script så finns det idag extremt stora rainbow tables för md5-hashes. Detta gör att många lösenord sparade med MD5-hash knäcks direkt om de kommer ut i orätta händer just för att man på förhand redan skapat miljontals md5-hashar att jämföra lösenorden mot.

Rainbow tables och Brute force

Rainbow tables är tabeller som innehåller färdiga hash-värden. Exempelvis skulle man kunna gå igenom alla tänkbara kombinationer av lösenord upp till 10 tecken och spara alla dessa hash-värden i en databas. När man sedan får tag i en MD5-hash så kör man den mot databasen för att se om man hittar samma hash. Om lösenordets längd tillsammans med saltets längd inte är över 10 tecken så kommer den att få en träff i databasen och ditt lösenord knäcktes på bara några minuter. Om webbplatsen inte använt sig av salt för att göra lösenorden längre så spelar det ingen roll vilka tecken du kombinerat om ditt lösenord är 10 tecken eller mindre. Det är därför viktigt att ha ett unikt salt värde att förlänga lösenorden med så att rainbow tables inte fungerar.

Brute force innebär att man skickar påhittade lösenord tills man knäcker lösenordet. Detta kan man göra genom att skicka inloggningsförfrågningar direkt mot hemsidan. Om inte webbplatsen kontrollerar antalet felaktiga inloggningar och blockerar för många felaktiga försök så spelar det ingen roll hur bra säkerheten är i övrigt. Då hänger det helt och hållet på hur bra lösenordet är. Men eftersom du som webbplatsägare har säkerheten i fokus så ser du till så att det inte är möjligt för den som attackerar att kunna göra så många felaktiga inloggningar utan att låsas ute.

För att lyckas köra en brute force attack där det inte går att göra för många felaktiga inloggningar på rad så krävs det att man har tillgång till hashen av lösenordet och funktionen som generar hashen. Men då krävs det att man får tillgång till både databasinformation och programkod. I ett sådant scenario finns några säkerhetsåtgärder du kan göra på förhand. Dels kan du tvinga användarna att ha ett svårt lösenord att gissa sig till vilket gör att själva brute force-metoden tar lång tid. Det andra är att se till det tar tid att generera hashen vilket förlänger brute force-attacken ytterligare.

Min hash-funktion för lösenord med PHP

Jag vill börja med att säga att jag använder inte exakt samma funktion och om du vill använda min funktion så rekommenderar jag att du ändrar lite på den så att den blir unik för dig.

Mitt mål med hash-funktionen var följande:

  • Använda en hash-algoritm som är bättre än MD5.
  • Inparametrar för lösenord och salt.
  • Skapa ett unikt saltvärde baserat på en del av lösenordet och det inmatade saltvärdet.
  • Göra funktionen långsammare för att försvåra brute force attacker.
  • Tvinga fram en minimumlängd som den slutgiltiga hashen baseras på.
  • Påtvinga att specialtecken används i den slutgiltiga hashen.

Jag tycker jag har lyckats väldigt bra och jag hoppas inte jag missat något uppenbart som gör den värdelös. Som hash-algoritm använder jag mig whirlpool som standard. Whirlpool genererar en 512bit hash-sträng och har ännu ingen känd brist vad jag kunnat hitta. Om man vill kan man använda sig av någon annan algoritm så som SHA-512 eller SHA-256. Jag har gjort det enkelt genom att ta med en inparameter för detta.

För att göra funktionen långsammare så generar jag en salt-sträng med hjälp av det inmatade saltvärdet + de tre sista tecknen i lösenordet (om lösenordet är mindre än tre tecken används en förbestämd sträng vilket aldrig ska ske ändå.). Dessa ingår i en loop som körs 500 gånger i exemplet för att ta fram en sträng som används som salt. Tanken här är att göra funktionen långsammare och som bonus skapar jag ett dynamiskt saltvärde som baseras på lösenordet som ingen mer än användaren känner till. Räknat lågt på 50 olika tänkbara tecken så ger det 125 000 (50³) möjliga saltvärden. Om någon mot förmodan skulle få tillgång till både källkod och hashsträngarna för lösenorden, och försöka köra en brute-force så tvingar jag dem att köra loopen som gör hash-funktionen långsammare. Alternativt får de prova varje lösenord med 125 000 olika salter vilket också gör det mycket långsammare. Oavsett har jag försvårat för dem.

Jag avslutar sedan funktionen genom att dela på det genererade saltet och lösenordet så att jag får totalt 4 olika strängar. Jag hashar sedan de delade lösenorden med det delade saltet och kombinerar sedan ihop alla strängarna + det inmatade saltet och ett par specialtecken som jag hårdkodat i funktionen för att generera den slutgiltiga hash-strängen med whirlpool.

Genom att ändra antal, hash-algoritm, antal loopar och hur de kombineras ihop så kan du skapa en egen unik funktion baserat på min kod.

<?php
/**
* Function to generate a unique hash-tag with whirlpool 512bit (128char long) as standard algorithm.
* Looping through 500 hashes with whirlpool (std) to slow down the process and get a unique salt value.
*/
function password_hash($password, $salt, $algo='whirlpool') {
// Generate a unique salt string to slow down the process
$new_salt = $salt;
for($i=0;$i<500;$i++)
$new_salt = hash($algo, ($salt.((strlen($password)>3)?substr($password,-3,3):'U9#').$new_salt));

// Use the first 30 characters as two different salts
$first_salt = substr($new_salt,0,20);
$second_salt = substr($new_salt,20,10);

// Split the password into two parts
$password_parts = str_split($password, (strlen($password)/2)+1);

// Generate hashes of the split passwords
$password_parts[0] = hash($algo, $first_salt.$password_parts[0]);
$password_parts[1] = hash($algo, $second_salt.$password_parts[1]);

// Create the password hash to use
$hashcode = hash($algo, $salt.'#'.$first_salt.$password_parts[0].$second_salt.'&s!'.$password_parts[1]);

return $hashcode;
}
?>

Hur säker är min hash-funktion?

Jag skulle vilja tro att min hash-funktion är väldigt säker, men jag hade gärna fått det bekräftat av en säkerhetsexpert. Eventuellt skulle man kanske använda mer än 3 tecken för att skapa fler tänkbara salt-alternativ. Oavsett vilken metod du använder dig av för att hasha lösenord så måste du ställa krav på användaren. Du kan inte låta dina användare använda lösenord som ”hej” exempelvis. Det skulle inte ta många sekunder att knäcka med brute force trots att jag gjort funktionen nästan 500 gånger långsammare.

På min utvecklingsserver tar det ca 0.00001 sekunder att generera en hashsträng med whirlpool. Min funktion exekveras på ca 0.0059 sekunder och istället för att kunna skapa 100,000 hashsträngar per sekund så kan man ”endast” skapa ungefär 168st per sekund med min funktion. Men då får man ha i åtanke att tiderna avser PHP-script och inte ett program skrivet i C. Processorn är också några år gammal och en modernare processor gör det mycket snabbare än så. Men den mest väsentliga skillnaden är att man kan använda sig av grafikkortens GPU då de är mångdubbelt snabbare än processorn på detta. Så med bra utrustning så är det betydligt fler än 168st per sekund.

För skoj skull kan vi räkna med att man genererar hash-strängar 100 gånger snabbare än min utvecklingsserver. Räknat på mitt tidigare exempel om att det endast finns 50 unika olika tänkbara tecken att använda sig av så skulle det se ut enligt följande tabell:

Lösenordslängd Maximal tid för brute force
4 tecken 6min 12sec
5 tecken 5h 10min
6 tecken 10d 18h
7 tecken 1y 173d
8 tecken 73y 266d

Någonting mindre än 7 tecken känner jag direkt är olämpligt. Men eftersom datorkraften hela tiden blir bättre hade jag nog krävt 8 tecken med variation av olika tecken för att kunna skydda användarnas lösenord så gott som möjligt. Men hur långt får man egentligen gå och när börjar det bli en viktig fråga om användarvänlighet?

Generera PDF-dokument med PHP

PDF-dokument har en väsentlig fördel framför exempelvis HTML, och det är att dokumenten ser likadant ut oavsett vilken läsare eller dator du användare för att öppna det på. Även om HTML strävar efter samma sak så är det tyvärr långt ifrån i praktiken. Detta gör att PDF-dokument lämpar sig väldigt bra till dokument som kanske anses ”för viktiga” för att endast visas i HTML. Ett exempel skulle kunna vara fakturor, rapporter eller andra viktiga genererade dokument.

Jag har själv använt mig av ett egenutvecklat script för att generera fakturor till PDF på automatik från PHP. Jag har använt mig av biblioteket PDFlib, men efter en ominstallation av Apache/PHP märkte jag att alla mina fakturor fick en exempeltext tvärs över hela dokumentet. Detta på grund av att jag inte har någon licens. Eftersom jag anser att licenserna är orimligt dyra så letade jag upp ett annat alternativ till PDFlib. PHP Pdf creation heter lösningen på mitt problem. Den stödjer fler funktioner än vad som fanns i PDFlib Lite som jag tror jag använde tidigare, och mycket mindre kod för att generera samma dokument som tidigare. En annan fördel är att PHP Pdf creation är endast en klass och inte en tillbyggnad till PHP som PDFlib är, vilket resulterar i att du behöver inte aktivera ‘extension’ för PDFlib i PHP.ini.

Efter drygt 2 timmars arbete har jag nu bytt från PDFlib till PHP Pdf creation. Det är inte speciellt svårt att jobba med PDF-dokument från PHP och jag rekommenderar det om ni har en tjänst/verktyg där det skulle kunna passa in. Exempelvis så är detta all kod ni behöver för ett ”Hello World”-dokument:

// Inkludera klass-filen 
include ‘class.pdf.php’;
// Skapa ett nytt objekt
$pdf = new Cpdf();
// Välj font och skriv ut ”Hello Word” med text-storlek 30
$pdf->selectFont(‘./fonts/Helvetica’);
$pdf->addText(30,400,30,’Hello World’);

// Skicka PDF-dokumentet till webbläsaren
$pdf->stream();

Skapa Thumbnails med ImageMagick

ImageMagick LogoImageMagick är en mjukvara med syfte att skapa och ändra bilder, men skiljer sig från program som Photoshop. ImageMagick bygger på att du anropar programmet genom en kommandotolk, så som ”Command Promt” i Windows eller motsvarande i andra operativsystem. Detta kan man med PHP uppnå med funktionen system() eller exec().

Det jag ville uppnå var att skapa thumbnails (tumnaglar) av större bilder när de laddas upp till webbplatsen. Tumnaglarna skulle dessutom ha absoluta mått oavsett om bilden var vertikal eller horisontal, och samma förhållande som original-bilden så den inte känns ut- eller ihopdragen. Med andra ord så vill jag att så mycket som möjligt av orginalbilden finns med i tumnageln och resten klipps bort för att hålla rätt aspect ratio.

Kommando till ImageMagick i Windows:
convert ”C:\orginal-image.jpg” -thumbnail 150×100^^ -gravity center -crop 150×100+0+0 +repage -format jpg -quality 75 ”C:\thumbnail.jpg”

Kommando till ImageMagick i Linux/Unix:
convert ‘/home/orginal-image.jpg’ -thumbnail 150×100^ -gravity center -crop 150×100+0+0 +repage -format jpg -quality 75 ‘/home/thumbnail.jpg’

PHP-funktion med kommando för Windows:
system(”convert \”C:\\orginal-image.jpg\” -thumbnail 150×100^^ -gravity center -crop 150×100+0+0 +repage -format jpg -quality 75 \”C:\\thumbnail.jpg\””);

Skillnaden mellan Windows och Linux är inte mycket. Jag har för vana att använda  apostrof istället för citattecken i Linux. Samt att man är tvungen att att använda escape character ^ för anropet i Windows.

För att snabbt förklara vad som händer så anropas convert men orginal-image.jpg som bild att behandlas. -thumbnail förminskar bilden  med hjälp av geometrin som nämns efteråt (150×100) och tar bort all meta-data i bilden. 150×100 är storleken på bilen som önskas och operator ^ (^^ i Windows) berättar för ImageMagick att geometrin är minsta möjliga. Bredden och Höjden får alltså aldrig vara mindre än det, men så nära som möjligt. Detta gör att antingen bredden eller höjden har uppnåt storleken vi vill ha men att där finns rester att klippa bort på bilden. Vi sätter därför fokus i mitten av bilden med -gravity center och klipper ut en bild i 150×100 pixlar av det som är kvar med -crop 150×100+0+0. De andra parametrarna +repage -format jpg -quality 75 ser till att det blir en bildfil (det som kliptes ut med -crop) och inte två filer, formatet på tumnageln blir JPEG och kvaliteten är 75 av 100 (ett värde jag tycker är rimligt att använda).