PHP krypto 1 - Hashing

Tags:    php sikkerhed kryptering hash
Skrevet af Robert Larsen @ 12.04.2012

Introduktion


Velkommen til første del i en lille serie om kryptografiske funktioner og anvendelser i PHP. Dette emne har længe været på ønskelisten her på udvikleren.dk, og jeg har arbejdet en del med det, så jeg synes, at det var oplagt at skrive lidt om det.

Jeg er IKKE hverken kryptolog eller matematiker, så jeg vil ikke forklare hele teorien bag, hvordan de forskellige algoritmer virker, men det er heldigvis heller ikke nødvendigt for at bruge dem. Jeg vil til gengæld forklare, hvad jeg synes, der er nødvendigt.

Teorien og anvendelsen er den samme for alle sprog, så det er ligemeget, hvilket sprog, du bruger. Jeg har bare valgt, at bruge PHP, fordi det var på ønskelisten, og fordi, jeg har mest erfaring med det.

Mine kodeeksempler vil være så korte, som jeg ser muligt. Dvs. at jeg ikke vil benytte databaser, hvis et simpelt array kan benyttes istedet, og alle kodeeksempler vil kunne udføres fra kommandolinjen, men det burde være nemt for en PHP udvikler, at bruge funktionerne fra en web server. Det er ikke en introduktion til PHP, som jeg regner med, at læseren i det mindste kan læse.

Jeg selv bruger Linux, og jeg vil også benytte et par kommandolinje værktøjer undervejs, som er standard eller lette at installere på Linux.

Nu er kryptografi et meget stort emne, som der er skrevet tykke bøger om, men jeg vil prøve at holde det så kort som muligt. Første stop på vejen er hashing funktionerne.

Hashing


Første artikel handler om hashing, fordi det er den mest anvendelige funktion indenfor kryptologien, og fordi de senere artikler om de andre emner bygger videre på hashing. Men hvad er hash (også kaldet checksum) ?

En hashing algoritme reducerer en mængde data gennem en envejs funktion til et byte array af fast størrelse, det såkaldte hash. Envejs funktionen gør dette nemt, men gør det umuligt at gå den anden vej, altså at finde det oprindelige data ud fra et hash.

Som et simpelt eksempel på en hashing algoritme kan vi tage den reducerede tværsum.

Tværsummen af et tal, er summen af tallets decimaler. Tværsummen af '427' er altså lig med '4 + 2 + 7 = 13'. Med den reducerede tværsum bliver vi ved, indtil vi har et resultat på kun ét decimal. Den reducerede tværsum af '427' er altså '1 + 3 = 4'.

Algoritmen er simpel og resultatet er altid det samme, når vi bruger samme input. Og det er en envejsfunktion, for vi kan ikke givet tallet '4' finde ud af, at det oprindelige tal var '427'.

Det er til gengæld ikke en særlig god hashing algoritme, for der er mange kollisioner og de er nemme at finde. En kollision forekommer, når to forskellige inputs giver samme hash, og det er ikke ønskværdigt, som vi skal se om lidt. Gode hashing algoritmer gør det svært at generere kollisioner.

Et andet krav til en god hashing algoritme er, at et hash for to næsten éns datasæt skal være markant forskellige.

Brugen af hashing


Men hvad kan hashing så bruges til ?

Nu er det én af de mest anvendelige kryptografiske funktioner, så listen er lang, men her er et par bud.

Tjek om to filer er éns
Hvis vi har to store filer (lad os sige 10 GB), og de ligger på to forskellige maskiner. Hvordan finder vi ud af, om de er éns uden at kopiere den ene fil fra den ene maskine til den anden ?

Man beregner et hash på hver maskine, og så ser man, om man kom frem til samme hash. Et hash er typisk på få hundrede bits, så det er meget små mængder data, vi snakker om.

Lad os lave et script, som genererer et hash for en fil.

Fold kodeboks ind/udPHP kode 


Der findes flere funktioner til at generere hashes, men 'hash_file' funktionen sørger for at læse hele filen i små bidder så vores 10 GB fil ikke skal indlæses fuldt til hukommelsen.

Brugen af ovenstående script er ret simpel:

Fold kodeboks ind/udKode 

'hash_file' har en tredje parameter, som bestemmer, om vi vil have en hex enkodet tekststreng eller om vi vil have de rå bytes.

Derudover uderstøtter 'hash_file' mange forskellige algoritmer, så det er nemt at ændre sin kode til at bruge en anden algoritme, hvis en sårbarhed bliver fundet. MD5 er faktisk ikke længere anbefalet som en sikker hashing algoritme.

Nu kan vi generere en MD5 hash af vores filer på de to maskiner og se, om de er éns. Nemt og bekvemt.

Tjek integriteten af mange filer
Forestil dig at du har bygget et stort website med tusindvis af filer. HTML/PHP/JavaScript/CSS og billeder. Hvordan opdager du, hvis en hacker ændrer på én af dem ?

Det gør du med et HIDS (Hostbased Intrusion Detection System). Sådan ét vil typisk bl.a. generere en (eller flere for at sikre os imod kollisioner) hash for alle filer, som ikke bør ændre sig og så tjekke filens hash med jævne mellemrum.

Vi kan lave et ultrasimpelt et af slagsen i PHP:
Fold kodeboks ind/udPHP kode 


Vi prøver den af på et af mine kode projekter:

Fold kodeboks ind/udKode 


Vi kan se, at jeg lavede en ændring i 'TowerOfHanoi.js' filen efter at jeg genererede 'tower.txt'.
Jeg er selvfølgelig nødt til at opdatere 'tower.txt' hver gang, jeg ændrer i min kode, eller lægger den op på min server, og 'tower.txt' må ikke kunne opdateres af hackerne, så den skal ligge et sted, hvor de ikke har adgang.

Obfuscering af passwords
Hashing på websites bliver oftest brugt til at "skjule" passwords. Hvis vi har en tabel, indeholdende brugernavn og password, vil denne tabel være et godt bytte for en hacker, som måske via en SQL injection sårbarhed vil kunne udlæse alle brugernes passwords.

Når man tager i betragtning, at de fleste bruger samme password flere steder, så vil dit site altså kunne give en hacker adgang til dine brugeres Facebook, GMail, Udvikleren.dk og andre sites, som de bruger. Det er selvfølgelig dårlig sikkerhed fra brugerens side, at han bruger samme password alle steder, men det havde ikke været et så stort problem, hvis dit site ikke havde givet hackeren password databasen.

Dét, man istedet kan gøre, er at gemme et hash af brugerens password. Givet samme input får man jo altid det samme hash, så om man tjekker på at '$real_password === $entered_password' eller på at '$password_hash === sha1($entered_password)' det giver ingen forskel.

Et lille eksempel kunne være:
Fold kodeboks ind/udPHP kode 


Vi prøver det:

Fold kodeboks ind/udKode 


Hvis en hacker får fat i vores database, så får han altså et større arbejde, eftersom vores passwords er scramblede. Men der er et problem.

Saltning af passwords
Mange brugere (også vores) bruger passwords, som kan slåes op i en ordliste. Hackeren kan ikke direkte tage et hash og derudfra finde det oprindelige password, men han kan generere hashes for alle ord i en liste, eller generere alle bogstavkombinationer op til et vist maximum og så se, om der er noget, som matcher.

Både 'secret' og 'hej' vil kunne findes på den måde. Det tager lang tid at generere alle bogstavkombinationer op til f.eks. 8 tegn, men det kan man jo gøre på forhånd og endda sætte et stort antal maskiner til det.

Man kan på den måde lave en database over alle SHA hashes for alle bogstavkombinationer (printable characters) på f.eks. 0-8 tegn og så "bare" slå hashet op. Dette kaldes en "rainbow table", og de fylder typisk mange terabytes. Men det giver jo god mening, hvis man lever af at hacke.

Hvad kan man gøre ?

Man kan salte sit password. Et salt er en "tilfældig" tilføjelse til hashet. Hvis vi tilføjer 'abcde' til alle passwords, så bliver tabellen jo anderledes. Vi prøver en opdateret version af forrige program (kun UserDatabase klassen vises herunder, resten er uændret):

Fold kodeboks ind/udPHP kode 


Der er kun fire ændringer i det ovenstående kode og det virker stadig:

Fold kodeboks ind/udKode 


Som I kan se, så er hashene ændret. Men det ene password, 'hej', er meget kort og tilføjet vores salt bliver det jo en hash af 'abcdehej', som er på otte tegn og derfor indenfor rækkevidden af en rainbow table. Men der er jo intet, der forhindrer os i at have følgende salt: 'Qn>yxTU.>/1nNMNQjyZ\!QW88tWpV54UWt\%t_kLSt))aS$l$w(x+t3M@NP^/Kd7'

Det er så selvfølgelig meningen, at man holder saltet hemmeligt. Hackeren skal nu udover at få adgang til databasen også finde ud af, hvad saltet er, og hans rainbow table er sandsynligvis intet værd.

Men der er stadig et problem.

Dynamisk saltning af passwords
Hvis to brugere har samme password (og det har 'robert' og 'hugo'), så får de også samme hash. Det er jo defineret, at med samme input, får man samme output. Hver gang!

Men der er jo intet, som forhindrer os i at bruge et brugerspecifikt salt. Vi kan jo smide brugernavnet og bruger id'et ind i saltet også. Bare det er data, som ikke ændrer sig. Man kan også under brugeroprettelsen generere et tilfældigt tal, som gemmes i bruger databasen, og som tilføjes saltet.

Her er en ny version af UserDatabase klassen:
Fold kodeboks ind/udPHP kode 


Igen, meget få ændringer. Vi prøver igen:

Fold kodeboks ind/udKode 


Det virker fint og man kan ikke længere se, at 'robert' og 'hugo' bruger samme password.

Hashing til brug i kryptografi
Udover de ovennævnte funktioner bruges hashing bl.a. til nøglegenerering til symmetriske krypterings algoritmer og til digitale signaturer, men det kommer vi tilbage til i senere artikler.

Afslutning


Så nåede du til vejs ende i denne første artikel om kryptografi i PHP. Jeg håber, at det var noget, du kunne bruge, og at du vil følge med i næste artikel, hvor vi skal se på symmetriske krypterings algoritmer.

Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (8)

User
Jan Johansen (MB) @ 12.04.12 10:04
Fantastisk gennemgang også selv om man har styr på det meste i forvejen :)
User
Martin Rohwedder @ 12.04.12 15:21
Dejlig informativ og lærerig. Keep Up The Good Work!
User
Theis @ 13.04.12 13:57
Hey. Fin artikel. Lige et par bemærkninger.
Hash og checksum bliver normalt ikke brugt som synonym. Checksum bruges om koder som bruges til at tjekke om noget data er korrekt. Altså det underproblem.

Der findes både brug af hash til algoritmer og til kryptografi. De kryptografiske er teoretisk bedre, og forsøger at garanterer mod angreb. Men til gengæld er de også rigtig langsomme. Derfor kan man ofte opnå speedup ved at vælge algoritmiske hashes i stedet, hvis det altså ikke bryder med sikkerheden.
User
Robert Larsen @ 14.04.12 08:35
Det er rigtigt, at der findes hurtige men usikre hashing algoritmer, men det ér nu altså det samme, de gør. Som Wikipedia beskriver det:

"A checksum or hash sum is a fixed-size datum computed from an arbitrary block of digital data"

http://en.wikipedia.org/wiki/Checksum

Eller:

"The values returned by a hash function are called hash values, hash codes, hash sums, checksums or simply hashes."

http://en.wikipedia.org/wiki/Hash_function
User
Kristian Just Iversen @ 15.04.12 16:40
God artikel. Og en god idé med dynamisk saltning, som var ny for mig.
User
Daniel Mautone @ 19.03.13 19:11
Kunne man både anvende et dynamisk salt samt et statisk? Det ville vel højne sikkerheden?
User
Robert Larsen @ 19.03.13 21:29
@Daniel Mautone
Sagtens, det er det jeg gør i koden ovenfor...jeg er lidt træt af at jeg ikke beskrev crypt() funktionen, for den gør det helt rigtige, men den er lidt...kryptisk.

Den tager dit password som input, og output bliver så en streng, som indeholder en identifier for den brugte hashing algoritme, det hashede password, det brugte salt og evt. konfiguration af crypt. Den hasher også pr. default 5000 gange. Det giver ikke ekstra sikkerhed, for det kunne en angriber også, men det tager 5000 gange så lang tid at brute force.
Jeg tror, ??det er stor artikel . SEO er bedre for mig end PPC , artiklen er stor. I mit tilfælde , kan SEO gøre store indkomst i din virksomhed . Jeg er så glad for, at jeg fandt et firma, der gjorde den SEO job for mig . Så Hvis nogen har brug for dem jeg anbefale du denne fyre , fordi de er de bedste ( i mit tilfælde ) http://www.kvalitetsseo.dk/
Du skal være logget ind for at skrive en kommentar.
t