34
Tags:
php
sikkerhed
kryptering
hash
Skrevet af
Bruger #2695
@ 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 énsHvis 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.
- <?php
- //Filename: md5sum.php
- if (count($argv) < 2) {
- echo "Usage: php " . $argv[0] . " <file1> <file2> ...\n";
- } else {
- for ($i = 1; $i < count($argv); $i++) {
- $filename = $argv[$i];
- if (file_exists($filename)) {
- echo hash_file('md5', $filename) . ' ' . $filename . "\n";
- }
- }
- }
- ?>
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:
$ php md5sum.php
Usage: php md5sum.php <file1> <file2> ...
$ php md5sum.php ./md5sum.php /etc/hosts
867fb90b21cfa9f0469aec4ca68a961b ./md5sum.php
0883f4586be9cbdf3fbd2d62414b590f /etc/hosts
$
'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 filerForestil 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:
- <?php
- //Filename: hids.php
- if (count($argv) !== 2) {
- echo "Usage: php " . $argv[0] . " <directory> > hidsfile\n";
- echo " php " . $argv[0] . " <hids file>\n";
- } else if (file_exists($argv[1])) {
- $path = $argv[1];
- if (is_dir($path)) {
- create_checksums(realpath($path));
- } else {
- verify_checksums($path);
- }
- }
-
- //Denne funktion render igennem alle filer i den specificerede sti
- //og skriver deres MD5 og SHA1 hash samt filens fulde sti til standard out
- function create_checksums($path) {
- if ($d = opendir($path)) {
- while ($entry = readdir($d)) {
- //Vi skipper forbi de to biblioteker 'current' og 'parent'
- if ($entry !== '.' && $entry !== '..') {
- $full_path = $path . '/' . $entry;
- if (is_file($full_path)) {
- //Dette er en fil, så generer de to hashes
- echo hash_file('md5', $full_path) . ':' . hash_file('sha1', $full_path) . ":" . $full_path . "\n";
- } else {
- //Dette er et directory så generer checksums derunder også
- create_checksums($full_path);
- }
- }
- }
- closedir($d);
- }
- }
-
- //Denne funktion indlæser den specificerede fil og verificerer
- //at alle filer, som er listet deri findes og har de specificerede
- //hashes
- function verify_checksums($file) {
- //Åben filen
- if ($f = fopen($file, 'r')) {
- //Læs den linje for linje
- while ($line = fgets($f)) {
- //Opdel linjen i de to hashes og filens fulde sti
- $parts = explode(':', trim($line));
- if (file_exists($parts[2])) {
- //Filen eksisterer
- if (hash_file('md5', $parts[2]) !== $parts[0] ||
- hash_file('sha1', $parts[2]) !== $parts[1]) {
- //Mindst én hash har ændret sig
- echo $parts[2] . " has been modified\n";
- }
- } else {
- echo $parts[2] . " has been deleted\n";
- }
- }
- fclose($f);
- }
- }
- ?>
Vi prøver den af på et af mine kode projekter:
$ php hids.php
Usage: php hids.php <directory> > hidsfile
php hids.php <hids file>
$ php hids.php ../TowerOfHanoi > tower.txt
$ php hids.php tower.txt
/home/robert/code/TowerOfHanoi/src/TowerOfHanoi.js has been modified
$
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 passwordsHashing 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:
- <?php
- //Filename: login_hash.php
- class User {
- public $id;
- public $name;
- public $password_hash;
- public function __construct($id, $name, $password_hash) {
- $this->id = $id;
- $this->name = $name;
- $this->password_hash = $password_hash;
- }
- }
-
- //Dette er vores database over brugere
- class UserDatabase {
- private $users;
- public function __construct() {
- $this->users = array();
- }
-
- //Tilføjer en bruger med specificeret navn og password
- public function add($name, $password) {
- //ID bliver genereret automatisk
- $id = count($this->users);
- //Vi gemmer en SHA hash af brugerens password
- $this->users[$name] = new User($id, $name, sha1($password));
- }
-
- //Denne funktion returnerer et bruger objekt for brugeren med det
- //specificerede navn og password medmindre navn eller password ikke
- //matcher en bruger.
- public function login($name, $password) {
- $user = FALSE;
-
- if (isset($this->users[$name]) &&
- $this->users[$name]->password_hash === sha1($password)) {
- $user = $this->users[$name];
- }
- return $user;
- }
-
- //Denne funktion vil skrive alle registrerede informationer om vores
- //brugere ud til standard out.
- public function dump() {
- foreach ($this->users as $user) {
- echo $user->id . ':' . $user->name . ':' . $user->password_hash . "\n";
- }
- }
- }
-
- //Opret et database objekt
- $database = new UserDatabase();
- //Tilføj et par brugere
- $database->add('robert', 'secret');
- $database->add('mosterolga', 'hej');
- $database->add('hugo', 'secret');
- $database->add('admin', 'MegetHemmeligtOgLangtBedre._165');
-
- //Skriv vores informationer
- $database->dump();
-
- if (count($argv) > 2) {
- //Forsøg et login
- if ($user = $database->login($argv[1], $argv[2])) {
- echo "Du loggede ind.\n";
- } else {
- echo "Du loggede IKKE ind.\n";
- }
- }
- ?>
Vi prøver det:
$ php login_hash.php
0:robert:e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
1:mosterolga:c412b37f8c0484e6db8bce177ae88c5443b26e92
2:hugo:e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
3:admin:cd4670514558415390a394b5c31eb922c3f98894
$ php login_hash.php robert secreT
0:robert:e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
1:mosterolga:c412b37f8c0484e6db8bce177ae88c5443b26e92
2:hugo:e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
3:admin:cd4670514558415390a394b5c31eb922c3f98894
Du loggede IKKE ind.
$ php login_hash.php robert secret
0:robert:e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
1:mosterolga:c412b37f8c0484e6db8bce177ae88c5443b26e92
2:hugo:e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4
3:admin:cd4670514558415390a394b5c31eb922c3f98894
Du loggede ind.
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 passwordsMange 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):
- class UserDatabase {
- private $users;
- private $salt; //DENNE LINJE ER TILFØJET
- public function __construct() {
- $this->salt = 'abcde'; //DENNE LINJE ER TILFØJET
- $this->users = array();
- }
-
- //Tilføjer en bruger med specificeret navn og password
- public function add($name, $password) {
- //ID bliver genereret automatisk
- $id = count($this->users);
- //Vi gemmer en SHA hash af brugerens password
-
- //DENNE LINJE ER ÆNDRET
- $this->users[$name] = new User($id, $name, sha1($this->salt . $password));
- }
-
- //Denne funktion returnerer et bruger objekt for brugeren med det
- //specificerede navn og password medmindre navn eller password ikke
- //matcher en bruger.
- public function login($name, $password) {
- $user = FALSE;
-
- if (isset($this->users[$name]) &&
- //DENNE LINJE ER ÆNDRET
- $this->users[$name]->password_hash === sha1($this->salt . $password)) {
- $user = $this->users[$name];
- }
- return $user;
- }
-
- //Denne funktion vil skrive alle registrerede informationer om vores
- //brugere ud til standard out.
- public function dump() {
- foreach ($this->users as $user) {
- echo $user->id . ':' . $user->name . ':' . $user->password_hash . "\n";
- }
- }
- }
Der er kun fire ændringer i det ovenstående kode og det virker stadig:
$ php login_salt.php robert secret
0:robert:36525a74f9a9b8e8cd8bfdf7ec660fb46c6b93aa
1:mosterolga:7d280fc8c1ed2305dbc2cb33ffa30839312d8b5f
2:hugo:36525a74f9a9b8e8cd8bfdf7ec660fb46c6b93aa
3:admin:5d299c9ca15b10330c4efd7e4ff627c9d5018eb2
Du loggede ind.
$
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 passwordsHvis 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:
- class UserDatabase {
- private $users;
- private $salt;
- public function __construct() {
- //Brug ubehageligt salt
- $this->salt = 'Qn>yxTU.>/1nNMNQjyZ\!QW88tWpV54UWt\%t_kLSt))aS$l$w(x+t3M@NP^/Kd7';
- $this->users = array();
- }
-
- //Tilføjer en bruger med specificeret navn og password
- public function add($name, $password) {
- //ID bliver genereret automatisk
- $id = count($this->users);
- //Vi gemmer en SHA hash af brugerens password saltet med id, navn og vores faste salt
- $this->users[$name] = new User($id, $name, sha1($id . $name . $this->salt . $password));
- }
-
- //Denne funktion returnerer et bruger objekt for brugeren med det
- //specificerede navn og password medmindre navn eller password ikke
- //matcher en bruger.
- public function login($name, $password) {
- $user = FALSE;
-
- if (isset($this->users[$name])) {
- $u = $this->users[$name];
- if ($u->password_hash === sha1($u->id . $u->name . $this->salt . $password)) {
- $user = $this->users[$name];
- }
- }
- return $user;
- }
-
- //Denne funktion vil skrive alle registrerede informationer om vores
- //brugere ud til standard out.
- public function dump() {
- foreach ($this->users as $user) {
- echo $user->id . ':' . $user->name . ':' . $user->password_hash . "\n";
- }
- }
- }
Igen, meget få ændringer. Vi prøver igen:
$ php login_dyn_salt.php robert secret
0:robert:a793ccc62f222d0e818d724bb582ecb223f61cd3
1:mosterolga:f0b8df9939880720018e5372a798b5280b369a40
2:hugo:0db428d372e89c05cedd032a56803fad85c8d3ab
3:admin:2565648f1cba4590ab895d775d88bcd947706606
Du loggede ind.
$
Det virker fint og man kan ikke længere se, at 'robert' og 'hugo' bruger samme password.
Hashing til brug i kryptografiUdover 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)
Fantastisk gennemgang også selv om man har styr på det meste i forvejen
Dejlig informativ og lærerig. Keep Up The Good Work!
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.
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/ChecksumEller:
"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
God artikel. Og en god idé med dynamisk saltning, som var ny for mig.
Kunne man både anvende et dynamisk salt samt et statisk? Det ville vel højne sikkerheden?
@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.