2014-02-26 7 views
9

Ich habe ein paar ähnliche Fragen gesehen, die nicht ganz genau meinen genauen Anwendungsfall zu adressieren, und ich denke, ich habe die Antwort herausgefunden, aber ich bin ein totaler Noob, wenn es um Sicherheit geht , RSA und so ziemlich alles, was damit verbunden ist. Ich habe eine grundlegende Vertrautheit mit den Konzepten, aber all die tatsächlichen Implementierungen, die ich bis zu diesem Zeitpunkt gemacht habe, drehen sich nur darum, den Code von jemand anderem zu bearbeiten, anstatt meinen eigenen Code zu erstellen. Wie auch immer, hier ist, wo ich bin:SSL über Javascript

Ich weiß, dass Javascript eine inhärent schlechte Stelle ist, um Verschlüsselung zu tun. Jemand könnte Man-in-the-Middle Ihre Antwort geben und den JS beschädigen, so dass Sie am Ende unverschlüsselte Daten über die Leitung senden. Es sollte über eine HTTPS-SSL/TLS-Verbindung erfolgen, aber diese Art von Hosting kostet Geld und so tun die offiziellen signierten Zertifikate, die realistisch mit der Verbindung gehen sollten.

aber sagen, dass ich glaube, die Art, wie ich tun werde dies die Man-in-the-Middle-Schwäche von JS Verschlüsselung aufgrund der Tatsache umgeht, dass ich immer nur eine Sache, die Verschlüsselung (ein Passwort-Hash) für einen RESTful-Service-Aufruf und dann nur mit diesem Passwort-Hash, um Anfragen vom Client zu signieren, um sie als vom Benutzer stammend zu authentifizieren, den die Anfragen beanspruchen. Das bedeutet, dass der JS nur dafür verantwortlich ist, einen Passwort-Hash einmal bei der Erstellung des Benutzerkontos zu verschlüsseln, und wenn der Server diese Chiffre nicht entschlüsseln kann, dann weiß er, dass sie bereits vergeben wurde.

Ich werde auch einige Client-Informationen speichern, insbesondere die $_SERVER['REMOTE_ADDR'], um sicherzustellen, dass jemand nicht M-i-t-M die Registrierung selbst austauschen.

Ich verwende PHP openssl_pkey_ Funktionen, um einen asymmetrischen Schlüssel und die Cryptico Bibliothek auf der Client-Seite zu generieren. Mein Plan ist, dass der Benutzer eine "Vorregistrierungs" -Anfrage an den REST-Dienst sendet, was dazu führt, dass der Server einen Schlüssel erzeugt, den privaten Schlüssel und die Client-Informationen in einer durch die E-Mail-Adresse indizierten Datenbank speichert und dann antwortet mit dem öffentlichen Schlüssel.

Der Client verschlüsselt dann den Kennwort-Hash des Benutzers mit dem öffentlichen Schlüssel und sendet ihn als weiteren Anforderungstyp an den REST-Dienst, um die Registrierung abzuschließen. Der Server würde den Kennwort-Hash entschlüsseln und speichern, die Client-Informationen und den privaten Schlüssel ungültig machen, so dass keine weiteren Registrierungen unter Verwendung dieser Informationen durchgeführt werden könnten, und dann mit einem 200 Statuscode antworten.

Um sich anzumelden, würde ein Benutzer seine E-Mail-Adresse und sein Passwort eingeben, das Passwort würde während der Registrierung gehashed, an den Anfragetext angehängt und erneut gehashed, um eine Anfrage an einen Anmeldeendpunkt zu signieren, der anhängen würde den gespeicherten Hash-Code in den Anfragetext und hasht ihn, um die Signatur gegen die in der Anfrage zu validieren und so den Benutzer zu authentifizieren. Weitere Datenanforderungen an den Dienst würden demselben Authentifizierungsprozess folgen.

Fehle ich irgendwelche grellen Löcher? Ist es möglich, den $_SERVER['REMOTE_ADDR'] Wert auf etwas bestimmtes zu spoofen? Ich brauche nicht die IP-Adresse, um genau zu sein oder die gleiche wie wenn der Benutzer sich anmeldet, ich muss nur wissen, dass der gleiche Rechner, der 'vorregistriert' hat und einen öffentlichen Schlüssel bekommen hat, die Registrierung abgeschlossen hat und nicht Hijacker, der die Registrierung für sie unter Verwendung eines erschnüffelten öffentlichen Schlüssels abschließt. Natürlich, wenn sie das schaffen, haben sie das Konto bei der Erstellung über die Wiederherstellung hinaus gehackt und der berechtigte Benutzer wäre nicht in der Lage, die Registrierung mit seinem eigenen Passwort abzuschließen, was auch in Ordnung ist.

Bottom line, kann jemand immer noch meinen Service hacken, wenn ich für einen echten SSL-Host abzweigen? Habe ich Javascripts Schwächen als Verschlüsselungswerkzeug umgangen?

Während ich meinen Code schreibe und debugge, poste ich ihn hier, wenn jemand ihn benutzen will.Bitte lassen Sie mich wissen, wenn ich meine Seite für Angriffe offen lasse.

Dies sind die Funktionen, die Clientanforderungen anhand des Hashs in den Headern validieren, den privaten Schlüssel generieren, in der Datenbank speichern, mit dem öffentlichen Schlüssel antworten und den Kennwort-Hash entschlüsseln und überprüfen.

 public function validate($requestBody = '',$signature = '',$url = '',$timestamp = '') { 
      if (is_array($requestBody)) { 
       if (empty($requestBody['signature'])) { return false; } 
       if (empty($requestBody['timestamp'])) { return false; } 
       if ($requestBody['requestBody'] === null) { return false; } 

       $signature = $requestBody['signature']; 
       $timestamp = $requestBody['timestamp']; 
       $requestBody = $requestBody['requestBody']; 
      } 

      if (($requestBody === null) || empty($signature) || empty($timestamp)) { return false; } 

      $user = $this->get(); 

      if (count($user) !== 1 || empty($user)) { return false; } 
      $user = $user[0]; 

      if ($signature !== md5("{$user['pwHash']}:{$this->primaryKey}:$requestBody:$url:$timestamp")) { return false; } 

      User::$isAuthenticated = $this->primaryKey; 
      return $requestBody; 
     } 

     public function register($emailAddress = '',$cipher = '') { 
      if (is_array($emailAddress)) { 
       if (empty($emailAddress['cipher'])) { return false; } 
       if (empty($emailAddress['email'])) { return false; } 

       $cipher = $emailAddress['cipher']; 
       $emailAddress = $emailAddress['email']; 
      } 

      if (empty($emailAddress) || empty($cipher)) { return false; } 

      $this->primaryKey = $emailAddress; 
      $user = $this->get(); 

      if (count($user) !== 1 || empty($user)) { return false; } 
      $user = $user[0]; 

      if (!openssl_private_decrypt(base64_decode($cipher),$user['pwHash'],$user['privateKey'])) { return false; } 
      if (md5($user['pwHash'].":/api/preRegister") !== $user['session']) { return false; } 

      $user['session'] = 0; 
      if ($this->put($user) !== 1) { return false; } 

      $this->primaryKey = $emailAddress; 
      User::$isAuthenticated = $this->primaryKey; 
      return $this->getProfile(); 
     } 

     public function preRegister($emailAddress = '',$signature = '') { 
      if (is_array($emailAddress)) { 
       if (empty($emailAddress['signature'])) { return false; } 
       if (empty($emailAddress['email'])) { return false; } 

       $signature = $emailAddress['signature']; 
       $emailAddress = $emailAddress['email']; 
      } 

      if (empty($emailAddress) || empty($signature)) { return false; } 

      $this->primaryKey = $emailAddress; 

      $response = $this->makeUserKey($signature); 
      if (empty($response)) { return false; } 

      $response['emailAddress'] = $emailAddress; 
      return $response; 
     } 

     private function makeUserKey($signature = '') { 
      if (empty($signature)) { return false; } 

      $config = array(); 
      $config['digest_alg'] = 'sha256'; 
      $config['private_key_bits'] = 1024; 
      $config['private_key_type'] = OPENSSL_KEYTYPE_RSA; 

      $key = openssl_pkey_new($config); 
      if (!openssl_pkey_export($key,$privateKey)) { return false; } 
      if (!$keyDetails = openssl_pkey_get_details($key)) { return false; } 

      $keyData = array(); 
      $keyData['publicKey'] = $keyDetails['key']; 
      $keyData['privateKey'] = $privateKey; 
      $keyData['session'] = $signature; 

      if (!$this->post($keyData)) { return false; } 

      $publicKey = openssl_get_publickey($keyData['publicKey']); 
      $publicKeyHash = md5($keyData['publicKey']); 

      if (!openssl_sign($publicKeyHash,$signedKey,$privateKey)) { return false; } 
      if (openssl_verify($publicKeyHash,$signedKey,$publicKey) !== 1) { return false; } 

      $keyData['signedKey'] = base64_encode($signedKey); 
      $keyData['rsa'] = base64_encode($keyDetails['rsa']['n']).'|'.bin2hex($keyDetails['rsa']['e']); 
      unset($keyData['privateKey']); 
      unset($keyData['session']); 

      return $keyData; 
     } 
+0

Ich sah gerade [diese] (http://stackoverflow.com/questions/5724650/ssl-alternative-encrypt-password-with-javascript-submit-to-php-to-decrypt?rq=1) in mein verwandter Feed, und der Unterschied hier ist, dass die Anfrage mit einem verschlüsselten Passwort nur einmal pro Benutzer gesendet werden kann, nur damit der Server weiß, welcher Wert verwendet wird, um die Hashes zukünftiger Anfragen zu isolieren. Wenn es jemand geschafft hat, seine IP zu fälschen, um mit dem echten Nutzer zu spielen, könnten sie den Benutzernamen beanspruchen, aber niemals ein Konto entführen, das nach der Registrierung verwendet wird ... Ich denke ... – citizenslave

+2

Ich nehme die beiden Aspekte an bin unwohl mit ist; 1. Nein, wir können $ _SERVER ['REMOTE_ADDR'] nicht vertrauen, in einer Reverse-Firewall eines Unternehmens könnten alle Benutzer die gleiche IP (nach außen) haben, abhängig von ihrer Konfiguration. 2. In einem Nicht-SSL-Szenario können Sie nicht darauf vertrauen, dass die Leitung nicht gehört wird (besonders jetzt, da wir wissen, dass eine "Attacke" jemand in ihrem Firewall-Netzwerk sein könnte). Das soll nicht heißen, dass ich noch das komplette Angriffsszenario habe. –

+0

Danke für die schnelle Antwort. Gemeinsame IPs in einem privaten Netzwerk sind ein echtes Problem bei der Verwendung von '$ _SERVER ['REMOTE_ADDR']', aber es scheint mir immer noch der einzige Angriff zu sein, den öffentlichen Schlüssel aus der Vorregistrierungsantwort zu schnüffeln, um ihn zu verwenden verschlüsseln Sie Ihr eigenes Passwort anstelle des Benutzers und spoofen Sie die endgültige Registrierungsanfrage. Dann hätten Sie die Kontrolle über das Konto, aber der Benutzer hätte das nie. Ich bin nicht begeistert davon, diesen Weg offen zu halten, aber ich kann damit leben, vor allem wenn die einzige Möglichkeit, die entfernte Adresse zu fälschen, darin besteht, sich im selben privaten Netzwerk zu befinden. Nicht perfekt, aber akzeptabel. – citizenslave

Antwort

3

Sie versuchen, SSL-Zertifikate, die von einer Zertifizierungsstelle signiert wurden, durch benutzerdefiniertes JavaScript zu ersetzen. Ich bin kein Sicherheitsexperte, aber soweit ich weiß, ist die einfache Antwort, dass dies nicht möglich ist.

Die grundlegende Tatsache ist, dass im öffentlichen Internet kann der Server nicht vertrauen, was ein Client sagt, und ein Client kann nicht vertrauen, was der Server sagt, genau wegen Mann in der Mitte Angriffe. Der Grund, warum Zertifikatsautoritäten zu Beginn notwendig sind, besteht darin, eine Art unparteiische Vertrauensbasis aufzubauen. CAs sind carefully vetted von den Browser-Anbietern, und es ist das einzige Vertrauen, das derzeit im öffentlichen Internet verfügbar ist, obwohl es sicherlich not perfect ist.

-1

Ich bin neugierig zu erfahren, warum ein relativ preiswertes SSL-Zertifikat (wie das 1-Jahr von Digicert bei $ 175 USD) indiskutabel ist. Vor allem, wenn dies für ein Geschäft ist, ist $ 175/Jahr eine angemessene Kosten (es funktioniert zu etwa $ 12,60 USD/Monat).

+0

Es ist nicht für ein Geschäft, ich schreibe es für eine brachiale politische Partei. Es ist ehrenamtliche Arbeit für eine Non-Profit- und eine Lernerfahrung für mich, ehrlich gesagt. Ich gebe zu, dass ein SSL-Zertifikat vorzuziehen ist, aber $ 175/Jahr kauft uns eine Menge Literatur und Standfläche. Wir machen auch nichts, was Sicherheit erfordert, wie eCommerce oder den Sturz von Miley Cyrus, also geht es eher darum, bösartige Script-Kiddies zu entmutigen, als entschlossene Cyber-Kriminelle aufzuhalten, und das Budget ist knapp bemessen. – citizenslave

+0

Außerdem bin ich mir ziemlich sicher, dass der Flow, den ich hier entworfen habe, nur bei der Account-Registrierung für eine ziemlich spezifische Art von Angriff angreifbar ist, und die meisten Features, für die Sicherheit sogar wirklich wichtig wäre, werden hinter zusätzlichen Formularen geschützt Authentifizierungsmethoden wie E-Mail- und Person-zu-Person-Code-Überprüfungen, so dass diese Funktionen sogar die Sicherheitslücke bei der Registrierung ausgleichen. Ohne einen Groschen extra, viel weniger 12,60 $/mo. Welches ist ein Domainname, der auf eine kostenlose Blogger-Website für einen Kandidaten verweist, wenn wir über Opportunitätskosten sprechen. – citizenslave

+0

Dies beantwortet die Frage nicht, sollte in einem Kommentar sein –