0251 / 590 837 15

Verschlüsseln und Entschlüsseln in PHP

Wer Zugangsdaten oder besonders vertrauliche Daten (Bankdaten) zum Beispiel in eine Datenbank speichern möchte, sollte diese nach Möglichkeit verschlüsseln. Wie man das mit PHP macht, möchte ich euch heute zeigen.

  1. Verschlüsseln durch Hashes
    1. Schutz von Rainbow-Tables
    2. password_hash ab PHP 5.5
  2. Verschlüsseln und Entschlüsseln mit Mcrypt
    1. mcrypt_encrypt
    2. mcrypt_decrypt
  3. Vorsicht vor eigenen Verschlüsselungsalgorithmen
  4. Verschlüsselung in Open Source Projekten

„Verschlüsseln“ durch Hashes

Wenn man einen Wert nur zum späteren Vergleich verschlüsseln möchte, sollte man dies über einen Hash erledigen. Ein Hash ist eine Art Prüfsumme, die aber den Ursprungsdatensatz nicht wiederherstellen lassen kann. Wie in den Kommentaren bereits erwähnt, ist es aus diesem Grund auch keine richtige Verschlüsselung, sondern eher eine Verschleierung.

Ein gutes Beispiel hierfür sind Passwörter. Durch die Speicherung als Hash kommt der Angreifer so nicht an die echten Passwörter der Benutzer. Beim Überprüfen des Passwort bildet man einfach auf die gleiche Weise einen solchen Hash und vergleicht diese miteinander.

Die zwei bekanntesten Hash-Funktionen in PHP sind:

Denen muss man einfach einen String übergeben und bekommt diesen verschlüsselt. Bei meinem Standard-Passwort würde das dann so aussehen:

<?php echo md5("Passwort123"); ?>

Schutz von Rainbow-Tables

Die beiden Funktionen sind nicht gerade neu und so gibt es findige Leute, die sich Listen dieser Hashes anlegen. Diese nennt man Rainbow-Tables. So werden die Hashes einfach über sehr viele Passwörter gebildet und zusammen mit den Ursprungswert gespeichert. Bei einem Angriff, können die Angreifer nun die Hashes aus unserer Datenbank nehmen und mit ihrer Rainbow-Table abgleichen. Alle User die dann zu einfache Passwörter haben können abgeglichen und somit „entschlüsselt werden„.

Aus diesem Grund sollte man das Passwort vorher komplizierter machen, so dass die Angreifer die Werte vermutlich nicht mehr in ihren Datenbanken haben. Das könnte man zum Beispiel so machen:

<?php 
	$passwort = "Passwort123";
	$salt     = "Das Salz in der Suppe";
	echo md5($passwort.$salt); 
?>

Das Passwort wird einfach durch einen von uns festgelegten Code (auch Salt genannt) erweitert und dadurch komplizierter. Jetzt muss der Angreifer den Text „Passwort123Das Salz in der Suppe“ bereits in seiner Datenbank haben, was dann nicht mehr ganz so wahrscheinlich ist. Je känger und komplexer der Salt ist, desto besser.

Neben dem Salt könnte man das Passwort vorher auch noch mit anderen Funktionen bearbeiten. So kann man zum Beispiel hinter dem Salt auch nochmal einen md5 vom Ursprungspasswort packen oder Ähnliches. Je kreativer ihr seit, desto besser. Allerdings solltet ihr aufpassen, dass ihr durch die Änderungen es nicht ermöglicht, dass auch andere Passwörter auf den gleichen Hash kommen können.

password_hash ab PHP 5.5

Wer bereits PHP 5.5 installiert hat, kommt in dem Genuss die Funktionen password_hash und password_verify zu nutzen. Diese sind wie Ben in den Kommentaren erwähnt hat dem normalen md5 vorzuziehen, weil sie eine größere Sicherheit bieten. Ein Beispiel:

<?php
	$hash = password_hash('passwort');
	if(password_verify('passwort',$hash))
	{
		echo "passt!";
	}
?>

In der simpelsten Form kann man einfach sein Passwort übergeben und später mit password_verify überprüfen. password_hash bietet aber noch einige Parameter, mit denen man unter anderem einen Salt hinzufügen kann. Weiteres bekommt ihr aus der Doku.

Wer noch kein PHP 5.5 hat, kann auch auf bcrypt zurückgreifen.

Verschlüsseln und Entschlüsseln mit Mcrypt

mcrypt_encrypt

Mcrypt ist eine PHP-Erweiterung, die für die Verschlüsselung und Entschlüsselung zuständig ist. Sie dürfte in der Regel bei euch schon installiert sein. Falls nicht, könnt ihr bei php.net nachlesen, wie man das nachholt. Mcrypt bietet von Haus aus den Zugang zu mehreren bekannten Verschlüsselungs-Algorithmen.

Als Beispiel nehmen wir mal einen der bekanntesten Algorithmen: AES (MCRYPT_RIJNDAEL_256)

<?php
   $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); 
   $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); 
   echo mcrypt_encrypt(MCRYPT_RIJNDAEL_256, "passwort", "Ich bin geheim!", MCRYPT_MODE_ECB, $iv); 
?>

Der Text „Ich bin geheim!“ wird hier mit dem Passwort „passwort“ verschlüsselt. Dies braucht man später, um es wieder zu entschlüsseln.

Zum Start der Verschlüsselung ist ein Initialization Vector (IV) erforderlich. Das sind die ersten zwei Aufrufe. Mit mcrypt_encrypt verschlüsselt man dann letztendlich seinen Text. Die Konstante wird der Modus der Verschüsselung festgelegt (nicht die Art). So können für bestimmte Datensätze andere Modi schneller sein (mehr dazu) Für die ersten Tests braucht ihr den Parameter aber noch nicht anzupassen.

Mit MCRYPT_RIJNDAEL_256 wurde der Algorithmus auf AES-256 festgelegt. Diese unterscheiden sich in der Stärke der Verschlüsselung. So würde zum Beispiel MCRYPT_RIJNDAEL_128 etwas schneller gehen, allerdings wäre es auch nicht ganz so sicher. Wenn ihr nur für euch persönlich verschlüsselt, würde ich einfach bei AES bleiben. Müsst ihr später Daten von fremden APIs oder Ähnliches entschlüsseln, kann es nötig sein, dies mit einen anderen Algotythmus zu tun. Eine Liste findet ihr hier.

mcrypt_decrypt

Um den Text wieder zu entschlüsseln, könnt ihr statt mcrypt_encrypt einfach mcrypt_decrypt aufrufen. Das sieht für unser Beispiel oben dann so aus:

<?php
	//Verschlüsseln
	$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB); 
	$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); 
	$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, "passwort", "Ich bin geheim!", MCRYPT_MODE_ECB, $iv); 
   
	//Entschlüsseln
	echo mcrypt_decrypt(MCRYPT_RIJNDAEL_256, "passwort", $encrypted, MCRYPT_MODE_ECB, $iv);
?>

Hier wird der verschlüsselte String in $encrypted gespeichert und sofort wieder entschlüsselt. Im Live-Betrieb würde die Variable dann zum Beispiel aus der Datenbank ausgelesen werden.

Vorsicht vor eigenen Verschlüsselungsalgorithmen

Es gibt immer wieder Leute, die versuchen ihre eigenen Verschlüsselungsalgorithmen zu erstellen. Natürlich muss ein Angreifer erst einmal verstehen, wie diese überhaupt funktionieren. Allerdings ist das Erstellen einer sicheren Verschlüsselung nicht gerade einfach. Bei vielen Versuchen ist sie einfach zu leicht knackbar. Am besten greift ihr auf die bestehenden Funktionen zurück. Wenn ihr wirklich noch etwas eigenes haben wollt, könnt ihr diese auch kombinieren.

Verschlüsselung in Open Source Projekten

Open Source Projekte sind leider auch für Angreifer gut durchschaubar. Deswegen muss man hier noch mehr aufpassen, was die Sicherheit angeht. Hier sollten die Passwörter und Salts nicht überall gleich sein, sondern wenigstens pro Installation der Open Source Software per Zufall generiert werden. Ansonsten können zum Beispiel Rainbow-Tables speziell für euere Software erstellt werden.

Dieses Zufalls-Passwort sollte im Idealfall auch nicht in der Datenbank gespeichert werden, da der Angreifer ansonsten durch die Datenbank immer noch an den Salt kommen kann. Eine reine Textdatei ist noch schädlicher, weil man diese im Browser einfach öffnen könnte.

Am besten generiert ihr ein PHP-Script, welches nur eure Salts in eine Variable zuweist. So wird es schwer ohne Dateizugriff daran zu kommen.

Verwandte Themen

Kommentare

Lars Ebert schrieb am 20.08.2013:

Achtung: md5 und sha sind nicht zum Verschlüsseln von Passwörtern gedacht, sondern eher, um eine Prüfsumme einer Datei schnell zu berechnen. Deshalb sind beide Funktionen auch so anfällig für Rainbow-Tables - da hilf leider auch ein Salt nichts! Bitte verwende niemals md5 für so etwas! Viel besser: bcrypt. Das ist nämlich extra dafür bestimmt, Passwörter zu verschlüsseln und basiert auf moderneren Algorithmen. Außerdem lässt sich die Komplexität (also quasi die Dauer des Verschlüsselns) einstellen. Je länger es dauert, das Passwort zu verschlüsseln, desto besser der Schutz. Für deinen Nutzer macht das "langwierige" Verschlüsseln beim Login nur ein paar Millisekunden aus, aber für einen Angreifer läppert sich das und die Berechnung der Rainbow-Tables dauert plötzlich nicht mehr nur einige Stunden sondern locker mal einige Jahre. Fazit: md5 nicht für Passwörter missbrauchen, sondern bcrypt nutzen! http://bcrypt.sourceforge.net

Ben schrieb am 20.08.2013:

Hallo, sehr lobenswert, sich mit diesem heiklen Thema zu befassen. Ich habe folgende Anmerkungen: * Hashes verschlüsseln nicht, sie VERSCHLEIERN. Das ist deshalb wichtig, weil das Wort "Verschlüsselung" nach Sicherheit klingt. Die bieten Hashes aber nicht, auch gesalzen nur begrenzt. Außerdem kann man - im Gegensatz zur Verschlüsselung - nicht den Originaltext rekonstruieren. * Für Passwörter: Bitte NIE NIE NIE NIE NIE mehr etwas anderes als http://de3.php.net/password_hash. password_hash() ist deshalb in PHP integriert worden, weil es zu viele Fehler von Anwendern mit bcrypt, mcrypt etc. gab. Dazu gibt es auch gleich password_verify(). Siehe dazu auch http://www.heise.de/security/meldung/PHP-5-5-soll-Passwort-Schlamperei-eindaemmen-1707355.html. * MD5 gilt als geknackt. Es sollte - auch in Beispielen - nicht mehr verwendet werden. Stell Dir vor, auf der Grundlage dieses Artikels werden Webanwendungen entwickelt. password_hash nutzt automatisch immer gute Hashfunktionen und gute Salts. * Weitere Hashes hatte ich hier mal aufgeschrieben: http://blog.bmarwell.de/grundlagen-kryptographie/ HTH, Ben

Stefan Wienströer schrieb am 20.08.2013:

Vielen Dank für die Hinweise! Habt natürlich Recht. Ich habe den Artikel dementsprechend erweitert.

Frank schrieb am 20.08.2013:

Genau die Millisekunden sind bei großen Anwendungen ein Problem. Angenommen es kommen 1000 Anfragen/sek rein, dann wären es schon mehrere Sekunden, die der Server alleine mit der Verschlüsselung zu tun hätte. Ich hatte auch einmal so ein Rijndael_128 gebaut wie oben dargestellt (gesalzen und gepfeffert). Jedoch musste das bei einem Seitenaufruf gleich 30 Mal gleichzeitig geschehen und da war selbst der schnelle Webserver überfordert und brauchte mehrere Minuten. Rijndael_256 konnte ich ganz vergessen, da stand der Server. Verschlüsseln ist ja ganz nett, nur muss es auch in Echtzeit passieren können. Nicht jeder hat nur eine kleine Webseite, die von kaum mehr als 2 Besuchern am Tag belästigt wird. Im Zeitbedarf und Rechenaufwand sehe ich noch Probleme mit immer besserer Verschlüsselung.

Stefan Wienströer schrieb am 20.08.2013:

Eine Lösung könnte sein, die Verschlüsselung im Hintergrund z.B. durch eine C-Anwendung zu regeln. Sprich du könntest nen Tool schreiben, was im Hintergrund auf dem Server läuft. Die Website schickt dann Username und Passwort an das Tool und dieses trägt es verschlüsselt in die Datenbank ein. In der Zwischenzeit läuft das PHP-Script weiter. Gibt dann nur leider noch das Problem mit dem wieder entschlüsseln, dass muss dann wirklich in Echtzeit passieren. Letztendlich muss man dann eine Balance zwischen Sicherheit und Performance finden.

Top 10 der Woche 34/13 « Wochenrückblicke schrieb am 25.08.2013:

[&#8230;] Verschl&#252;sseln und Entschl&#252;sseln in PHP &#8211; Wer Zugangsdaten oder besonders vertrauliche Daten (Bankdaten) in eine Datenbank speichern m&#246;chte, sollte diese nach M&#246;glichkeit ver-schl&#252;sseln. Wie man das mit PHP macht, m&#246;chte ich euch zeigen. Weiter&#8230; [&#8230;]

Ben schrieb am 18.12.2013:

Hi Stefan, ein C-Programm aus PHP aufrufen ist sicherlich keine gute Idee. Erstens ist der Code dann nicht mehr ohne weiteres portabel (OS-Abfragen in PHP - ernsthaft?), und zum anderen kostet das öffnen vieler paralleler Prozesse wohl viel mehr Zeit als das bisschen Hashen. Wichtig ist lediglich, dass der Rückweg einen hohen Aufwand besitzt.

Frank schrieb am 18.12.2013:

@Ben und @Stefan PHP wird mit C entwickelt, also ist die Idee, C für rechenintensive Dinge zu nehmen, durchaus sinnvoll. Aber PHP beitet ja bereits programmierte Funktionen an, die quasi vorkompiliert sind. Da PHP durch Zend Engine interpretiert wird, dauert es bis dann ein Maschinencode bei der CPU landet. Ein eigens und individuell kompiliertes C-Programm ist daher viel schneller als ein PHP-Script Konstrukt, das erst durch die Zend-Engine laufen muss. Und wenn wir sind doch beim Verschlüsseln und nicht beim Hashen, und individuell Verschlüsseln kostet ungleich mehr Zeit als hashen. Damit kann man locker eine Webseite lahm legen, da spreche ich aus Erfahrung. Dass es nicht mehr so einfach portierbar ist, ist vielleicht sogar ein ganz guter Aspekt in Sachen Sicherheit.

Christoph schrieb am 19.02.2014:

Hallo, dein Blog hat mir viel Zeit erspart, ich hoffe du machst weiter so. Und dann auch noch in Deutsch echt super ;) ;)

Alexander schrieb am 03.03.2014:

Hat es sinn, verschlüsselte Passwörter z.B. mit anderen Verschlüsselungsarten nochmal zu verschlüsseln?

Stefan Wienströer schrieb am 03.03.2014:

Nur bedingt, da man dadurch nicht unbedingt einen Vorteil hat. Besser direkt eine sichere Methode wählen.

Ben schrieb am 04.03.2014:

Um Stefans Antwort nochmal zu untermauern: Doppelte Verschlüsselung beliebiger Ausgangstexte macht die Verschlüsselung sogar noch eher angreifbar! Vor allem, wenn beide Verfahren den gleichen Key nutzen, ist es eher unsicher. Man sollte immer überlegen, wie sicher ein Verfahren sein sollte. Soll ein Text ca. 15 Jahre geheim bleiben, dürfte ein 2048 bis 4096bit PGP-Key (RSA) zum Verschlüsseln völlig genügen. In Datenbanken werden Passwörter nicht verschlüsselt abgelegt, sondern gehasht. Das hatte ich oben schon einmal erwähnt. Hintergrund: Man muss ja irgendwie das Login vergleichen können. Würde man die Passwörter entschlüsseln, so muss man also auch den kryptografischen Schlüssel speichern. Passwörter also bitte NIE verschlüsseln!

Jan schrieb am 23.02.2015:

Hallo Stefan, danke für den schönen Überblick mit passenden Beispielen!

Ben schrieb am 02.10.2015:

&gt; PHP wird mit C entwickelt, also ist die Idee, C für rechenintensive Dinge zu nehmen, durchaus sinnvoll. Java wird in C entwickelt, und du nutzt trotzdem native Bibliotheken. Das ist überhaupt keine Begründung. Du musst nämlich jedes mal einen neuen Prozess spawnen (für das C-Tool), und das kostet viel mehr Zeit als einen Thread zu öffnen (PHP-Skript). &gt; Aber PHP beitet ja bereits programmierte Funktionen an, die quasi vorkompiliert sind. &gt; Da PHP durch Zend Engine interpretiert wird, dauert es bis dann ein Maschinencode bei der CPU landet. &gt; Ein eigens und individuell kompiliertes C-Programm ist daher viel schneller als ein PHP-Script Konstrukt, das erst durch die Zend-Engine laufen muss. Der Block hat gut angefangen. Natürlich hat die Zend Engine einen Opcode-Cache und ist daher nach wenigen durchläufen sehr schnell - und natürlich ist es auch so, dass das Prozess-Spawnen wie gesagt so lange dauert, dass es sich im Vergleich zum Interpreter trotzdem (oft) nicht lohnt. &gt; Und wenn wir sind doch beim Verschlüsseln und nicht beim Hashen, und individuell Verschlüsseln kostet ungleich mehr Zeit als hashen. &gt; Damit kann man locker eine Webseite lahm legen, da spreche ich aus Erfahrung. Na klar - das ist richtig. Ich war noch beim Thema hashen, weil die erste Version des Artikels das Hashen ja noch als verschlüsseln bezeichnete. &gt; Dass es nicht mehr so einfach portierbar ist, ist vielleicht sogar ein ganz guter Aspekt in Sachen Sicherheit. Das ist nur security by obscurity. Ob du ein Programm nun auf einem anderen Rechner ausführen kannst oder nicht ist egal. Wenn der Hacker das Tool hat, dekompiliert er es. Es ist leider überhaupt kein Sicherheitsaspekt. Gruß, Ben