PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Applikationslogin mittels SSL-Zertifikat

with 6 comments

Passworte haben Nachteile, das wissen wir alle. Sie müssen genügend komplex aber trotzdem merkbar sein, die Länge und Art der Zeichen sind dafür verantwortlich wie einfach es zu erraten ist. Fast niemand verwendet Passworte mit mehr als 8 oder 10 Zeichen. Ohne wirksame Brute-Force-Gegenmaßnahmen sind diese innerhalb von Stunden oder wenigen Tagen knackbar. Auch ein Einbruch in den Webserver liefert dem Angreifer heutzutage häufig den Hash des Passwortes (häufig MD5).

SSL bzw. Public/Private-Key Verfahren zeigen sichere Alternativen. Diese sind häufig sehr lang (>1024 bit) und dadurch nicht mehr erratbar. Außerdem ist es möglich den Public-Key ohne Bedenken herauszugeben, daraus den Private-Key zu errechnen wird auf lange Zeit nicht möglich sein. Das ist zwar auch bei Passworten möglich (md5, sha1 etc), aber diese Hashes sind kürzer und in der Vergangenheit regelmäßig geknackt worden. Erhältliche Rainboxtables und Wörterbuch-Angriffe machen die Rückführung auf das Originalpasswort sehr einfach.

Wie wäre es also, zum Beispiel den Admin-Bereich einer Applikation nicht mittels Username+Passwort zu schützen, sondern mit einem OpenSSL-Keypaar? Dann braucht man sich keine Sorgen über Brute-Force-Angriffe machen.

Eine weitere schöne Anwendungsmöglichkeit ist, wenn sich die Administratoren als einer ihrer User einloggen können möchten. Da man ja die Passworte seiner User nicht kennt muss man das anders lösen.

Der Administrator loggt sich also in seinen Admin-Account ein und klickt auf einen Usernamen. Dieser Link enthält Daten und eine Signatur. In der Nutzer-Applikation kann die Signatur überprüft und dadurch authentifiziert werden. Es gibt kein Login-Formular und kein leicht zu erratendes Passwort das als Angriffspunkt offen steht. Wir nehmen hier an dass Admin-Applikation und Nutzer-Applikation auf unterschiedlichen Domains und Rechnern laufen.

Zur Verdeutlichung vielleicht nochmal ein kleines Schaubild:

Hier soll also genau der mittlere Pfeil ermöglicht werden, und zwar so sicher wie möglich. Dazu zählt auch, den privaten Schlüssel NICHT bei jedem Login durch das Internet senden zu müssen.

Die ersten Schritte mit PHP und OpenSSL sind sehr einfach. Da ich hier nicht auf die Details von Public-Key-Verfahren eingehen möchte zeige ich direkt, wie OpenSSL in PHP nutzbar ist:

<?php
// Erstellung des Schlüsselpaares
$res = openssl_pkey_new();

// Privaten Schlüssel extrahieren
openssl_pkey_export($res, $privatekey);

// Öffentlichen Schlüssel extrahieren
$publickey = openssl_pkey_get_details($res);
$publickey = $publickey["key"];

echo "Private Key:<br/><pre>$privatekey</pre><br/><br/>Public Key:<br/><pre>$publickey</pre><br/><br/>";

$cleartext = '1234 5678 9012 3456';

echo "Clear text:<br/>$cleartext<br/><br/>";

openssl_public_encrypt($cleartext, $crypttext, $publickey);

echo "Crypt text:<br/>$crypttext<br/><br/>";

openssl_private_decrypt($crypttext, $decrypted, $privatekey);

echo "Decrypted text:<br/>$decrypted<br/><br/>";

Hier wird ein Schlüsselpaar erstellt, privater und öffentlicher Schlüssel extrahiert und dann ein Text mit öffentlichem Schlüssel verschlüsselt und mit dem zugehörigen privaten Schlüssel wieder entschlüsselt. Die Ausgabe sieht so aus:

Private Key:

-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDYs9NxbegXU7WOtqvNrkoiMYMdjDrSC7aAnf35EmgPwSxx8KA3
1XRMZ7pbJiqWebEXU2+Owr0KI6n7E4E7J/v4QxQGkOcgq20I5KZkCXZhheM0vB59
GpfS89PcCxopnX8rUOGrRBCqpzJ4HonjcrS7EG9mFqib6FeKqYRjdSkx7wIDAQAB
AoGBAJVOAObsFLbNxA/aKDEEXquEdZQMJBLIYyvmry/G0M/aBqdSZPFTLlfeN/XJ
LBqVKcCqifhQkDLGM717yNRbTiyPwTwfyXlAx1CO6D+29O69RUuRkre/83enRLMB
1Odt3H3RlNo0pIxExPOU1gf2oRUe/V6DkOn0TxI+3Gxly0GBAkEA+xMLqZlV0Ny/
t1Zw2wRTZB/lNmuK2m3ckwZ216bEfSiE8Gtr7MIevd7+8BDZYIQf0XwBw/rmeOh2
xOEAhNNWjwJBANz0KAnG0ZZyer0HeqFFikUSgjPaSPeiCHWp/lSvkFW2IyC13Drd
SaZ8u+DqmyB1r1OqDeFzaA0bm77o4Du1HqECQQCDcFwJpIlFd1syWqFoNyKE5yGy
1KfzY9I2cgrjKJ3yu7SkvEfawWEgm04xVVDHc0PJAjdWZtIi9+e9d+EyqATHAkEA
yA69rdR58nmnDj6WVy1Ku629fEuZo7XvaPJJWM45ppGqjrR7OkUgqYDo8AYb/TDx
VZR0yvweazfjNeFPHmCo4QJAIc4plzg1LfgaL26Ce9z8+IVkRmp6Ez04UJ6rSYNZ
Ib9EVeVxD56Y/erQJpywhS24OK2IAnCcLR1Ap8HNgIIsIQ==
-----END RSA PRIVATE KEY-----

Public Key:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYs9NxbegXU7WOtqvNrkoiMYMd
jDrSC7aAnf35EmgPwSxx8KA31XRMZ7pbJiqWebEXU2+Owr0KI6n7E4E7J/v4QxQG
kOcgq20I5KZkCXZhheM0vB59GpfS89PcCxopnX8rUOGrRBCqpzJ4HonjcrS7EG9m
Fqib6FeKqYRjdSkx7wIDAQAB
-----END PUBLIC KEY-----

Clear text:
1234 5678 9012 3456

Crypt text:
Ÿ‡ä üN;v½2¢F;‚΋z@§þwŽyB=ú®C¿¢F;‚΋z@§þwŽy2¢F;‚΋z@§þwŽy2¢F;‚΋z@§þwŽy

Decrypted text:
1234 5678 9012 3456

Wir werden nun 2 Scripte benötigen: ein Script für die Admin-Applikation, das einen Link generiert (mit dem privaten Schlüssel) den die Nutzer-Applikation verifizieren kann (mit dem öffentlichen Schlüssel). Wenn die Signatur mit Public-Key 2 verifiziert werden kann, kann die Signatur nur mit dem Private-Key 2 erstellt worden sein. Die Authentifizierung ist erfolgreich.

Die index.html unter /admin sieht sehr unspektakulär aus:

<a href="generateLink.php?userId=1">Login als User 1</a><br/>
<a href="generateLink.php?userId=2">Login als User 2</a><br/>
<a href="generateLink.php?userId=3">Login als User 3</a><br/>

Der Inhalt von generateLink.php in der Admin-Applikation sieht dann so aus:

<?
// aktuell eingeloggter Administrator
$myAdminId = 1;
$privateKey = file_get_contents('keys/admin' . $myAdminId . '.priv');

$data = $_GET['userId'] . '|' . $myAdminId . '|s3cr3tS4lth3r3';

$binarySignature = '';
openssl_sign($data, $binarySignature, $privateKey, OPENSSL_ALGO_SHA1);

header('Location: /user/backdoor.php?userId='.$_GET['userId'].
        '&adminId='.$myAdminId.
        '&signature='.urlencode(base64_encode($binarySignature)));
?>

Wir holen uns also den privaten Schlüssel des aktuell eingeloggten Administrators und signieren damit einen String ($data).

Die „Backdoor“ in der Nutzerapplikation, die normale Nutzer nicht zu Gesicht bekommen (und selbst wenn: sie bräuchten noch den privaten Schlüssel eines Admins und den Inhalt von $data um sich einzuloggen):

<?php
if (!isset($_GET['userId']) || !isset($_GET['adminId']) || !isset($_GET['signature'])) {
    header("HTTP/1.0 404 Not Found");
    exit;
}

session_start();

$data = $_GET['userId'] . '|' . $_GET['adminId'] . '|s3cr3tS4lth3r3';
$binarySignature = base64_decode($_GET['signature']);

$publicKey = file_get_contents('keys/admin'.$_GET['adminId'].'.pub');

$ok = openssl_verify($data, $binarySignature, $publicKey, OPENSSL_ALGO_SHA1);

if ($ok == 1) {
    $_SESSION['userId'] = $_GET['userId'];
    header('Location: /user/index.php');
} elseif ($ok == 0) {
    echo "ungültig, irgendwas lief schief\n";
} else {
    echo "Error während der Prüfung der Signatur\n";
} ?>

Hier prüfen wir zuerst ob alle benötigten Parameter vorhanden sind. Natürlich kann und sollte man das noch besser prüfen, würde hier aber den Rahmen sprengen. Mit Hilfe des öffentlichen Schlüssels prüfen wir dann die Signatur, nämlich ob $data mit dem zum öffentlichen Schlüssel gehörigen privaten Schlüssel signiert wurde. Dann setzen wir die wichtige Session-Variable, in der steht wer aktuell eingeloggt ist, in diesem Fall der entsprechende User.

/user/index.php zeigt dann, wer aktuell in der Nutzer-Applikation eingeloggt ist:

<?  session_start(); ?>
Dies ist die Nutzerapplikation, du bist
<?  if (isset($_SESSION['userId'])) { ?>
        eingeloggt als User <?=$_SESSION['userId'] ?>.
<?  } else { ?>
        nicht eingeloggt.
<?  } ?>

Extrem wichtig ist es natürlich, die Admin-Applikation vor Zugriff zu schützen, insbesondere der /admin/keys Ordner mit den Private Keys. Das ist aber bei Admin-Interfaces immer so, hier sollte mit SSL-Client-Zertifikaten, IP-Beschränkungen, VPN-Zugriff etc. gearbeitet werden.

Der /user/keys Ordner ist etwas zwiespältig, denn dort liegen nur die Publik Keys. Diese sind eigentlich nicht schützenswert, damit kann ein Angreifer nicht viel anfangen. Was man jedoch unterbinden sollte ist den schreibenden Zugriff durch einen Hacker. Wenn er dort seinen eigenen Public Key ablegen kann würde er Zugriff bekommen.

Verbesserungsvorschläge sind gern willkommen!

EDIT: Wenn ich so drüber nachdenke würde ich in $data auch noch einen Zeitstempel einfließen lassen, damit sich die Signatur ändert und nicht immer die selbe ist. Bei der Prüfung schaue ich dann dass der Zeitstempel maximal 10 Sekunden in der Vergangenheit liegt (synchronisierte Uhren vorausgesetzt).

Privaten Schlüssel extrahieren

Written by Michael Kliewe

Mai 14th, 2010 at 12:35 pm

6 Responses to 'Applikationslogin mittels SSL-Zertifikat'

Subscribe to comments with RSS or TrackBack to 'Applikationslogin mittels SSL-Zertifikat'.

  1. Hmmm… und wo kommt der private key im Moment des „Einloggens“ her?! Ich meine der Kunde besucht eine Seite und will sich authentifizieren, wie läuft das ab?

    fred

    fred

    14 Mai 10 at 20:45

  2. Das Szenario habe ich hier nicht vorgestellt, du meinst den unteren Pfeil in dem Diagramm. Dazu kommt man wohl nicht drumherum, den Private Key beim Einloggen mit hochzuladen. Geht zwar, aber wenn der Server gehackt wird hat der Angreifer den Private Key in seinen Händen. Wenn man das Risiko in Kauf nimmt und https verwendet, um ein Belauschen der Verbindung auszuschliessen, geht das.

    Oder man verwendet ein SSL-Client-Zertifikat, das während des https-Verbindungsaufbaus verwendet wird. Der Server sendet einen Text, den der Browser signiert und zurücksendet. Dann kann der Server prüfen ob die Signatur zu einem Zertifikat passt. Dadurch kann der Webserver den Client eindeutig identifizieren. Das ist aber soweit ich weiß eine Sache, die der Webserver mit dem Browser aushandelt. Da kann man mit PHP nichts machen.

    Eventuell wäre eine Lösung mittels Javascript möglich, aber darüber habe ich noch nicht nachgedacht. Man kopiert seinen Private-Key in ein Textfeld und via Javascript wird ein Text signiert und zum Server gesandt. Dieser kann dann prüfen ob die Signatur gültig ist. Der Private Key verlässt den Client nicht. Könnte gehen, ist aber nicht sehr nutzerfreundlich, jedes Mal seinen Private Key in ein Textfeld zu kopieren. Besser ist da das oben erwähnte Client-Zertifikat, der Browser regelt das dann automatisch.

    Michael Kliewe

    15 Mai 10 at 00:20

  3. prinzipiell interessant, aber in der Praxis imho nicht nutzbar. Ich kann ja schlecht meinen privatekey durch die Lande schicken. Das wäre selbst bei ner SSL-Leitung unsinnig.

    Sinnvoll wäre das nur in Verbindung einer SSL-Client-Zertifikat-Prüfung über den Browser wie in deinem Kommentar angedeutet.

    Bastian

    19 Mai 10 at 08:43

  4. @Bastian: Deshalb habe ich auch nur die Version vorgestellt wo man keinen Key durchs Netz senden muss, sondern nur eine Signatur von einem Server (oder Applikation) zum anderen gesendet wird.

    Michael Kliewe

    19 Mai 10 at 09:12

  5. Hallo!

    Danke für die (für mich) ausführlichen Infos. Was ich allerdings noch nirgends gefunden habe, ist ein Script, das den SSL-Login per Client-Zertifikat triggert, bzw. eine WebSite dazu bringt, ein SSL-Zertifikat zur Authentifizierung anzufordern…

    War ich bisher zu blind? Genau das ist doch das interessante!

    Wie bekomme ich den Browser des Login-Kandidaten dazu, dass er die SSL-Verbindung mit einem Client-Cert aufbaut, bzw. als Logindaten ein SSL-Zert schickt, dass ich verifizieren und einem Benutzer / einer Benutzergruppe zuordnen kann?

    Die Informationen im Client-Zertifikat (x.509) enthalten Strings, mit denen man einen Benutzer/ eine Benutzergruppe identifizieren kann.

    Nachdem man weiss, wer sich einloggen will, sendet die Site einen One-Time-String der mit dem öffentlichen UserKey und dem privaten SiteKey verschlüsselt wurde.

    Wenn dann der Client-Browser eine Antwort mit dem One-Time-String sendet, die mit dem öffentlichen Site-Schlüssel und dem eigenen privaten Schlüssel verschlüsselt wurde, dann wissen beide Seiten, dass alles OK ist und der, der sich anmelden möchte, auch der ist, der er behauptet zu sein.

    Die Sicherheit ergibt sich aus der Tatsache, dass die einzigen Schlüssel, die übers Netz gehen, die öffentlichen sind, die sowieso jeder haben darf / soll. Sofern die Zertifikate auf Gültigkeit geprüft werden können, ist alles geritzt: Sicherheit, Identität und Gültigkeit!

    Ergänzende Infos gerne per PM!

    Gruß
    Marc

    Marc

    9 Feb 12 at 09:30

  6. @Marc: Danke für die Frage, hier vielleicht ein passender Artikel zum Thema Client-Zertifikate:
    http://www.phpgangsta.de/client-zertifikate-als-sicherer-login-ersatz

    Michael Kliewe

    11 Feb 12 at 11:20

Leave a Reply

You can add images to your comment by clicking here.