Ende-zu-Ende-Verschlüsselung in Javascript, PHP und MySQL

[ad_1]

Ende-zu-Ende-verschlüsselte Chats sind sicherer als solche, bei denen die Verschlüsselung serverseitig erfolgt. Denn die Nachrichten werden bereits verschlüsselt, bevor sie an den Server gesendet werden. Dadurch wird jeglicher Lese- oder Änderungsvorgang von Nachrichten während der Übertragung verhindert. Lassen Sie uns lernen, wie es geht.

Wir werden Javascript zur Ver- und Entschlüsselung verwenden. Und wir werden PHP für die Bearbeitung von AJAX-Anfragen verwenden. Alle verschlüsselten Nachrichten werden in der MySQL-Datenbank gespeichert.

Inhaltsverzeichnis

  1. Datenbank einrichten
  2. Private und öffentliche Schlüssel
  3. Nachricht verschlüsseln
  4. Nachricht entschlüsseln

Datenbank einrichten

Öffnen Sie zunächst Ihr phpMyAdmin und erstellen Sie eine Datenbank mit dem Namen end_to_end_encryption. Erstellen Sie dann eine Datei mit dem Namen db.php und schreibe den folgenden Code hinein.

<?php

$conn = new PDO("mysql:host=localhost;dbname=end_to_end_encryption", "root", "");

Der zweite und dritte Parameter sind Benutzername und Passwort für die Datenbank. Sie können sie entsprechend Ihrem Server ändern. Dann erstellen wir eine Datei mit dem Namen index.php und schreibe den folgenden Code hinein.

<?php

require_once "db.php";

$sql = "CREATE TABLE IF NOT EXISTS users(
  id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  privateKey TEXT DEFAULT NULL,
  publicKey TEXT DEFAULT NULL
)";
$conn->prepare($sql)->execute();

$sql = "CREATE TABLE IF NOT EXISTS messages(
  id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
  sender VARCHAR(255) NOT NULL,
  receiver VARCHAR(255) NOT NULL,
  message TEXT NOT NULL,
  iv TEXT NOT NULL
)";
$conn->prepare($sql)->execute();

?>

Dadurch werden 2 Tabellen erstellt. Eine für Benutzer, in der wir den privaten und öffentlichen Schlüssel jedes Benutzers speichern. Zweite Tabelle, in der wir alle unsere verschlüsselten Nachrichten speichern. Wir speichern auch IV (Initialisierungsvektor), der zum Entschlüsseln der Nachricht erforderlich ist. Die IV wird ebenfalls verschlüsselt. Führen Sie die folgende URL im Browser aus.

http://localhost/end-to-end-encryption-js-php-mysql/index.php

Sie müssen zwei Benutzer manuell in die Benutzertabelle einfügen, um den Mechanismus der Ende-zu-Ende-Verschlüsselung richtig zu verstehen.

Wir gehen davon aus, dass Sie einen Ordner mit dem Namen haben Ende-zu-Ende-Verschlüsselung-js-php-mysql wo du deine platziert hast index.php Datei. Nachdem Sie die obige URL im Browser ausgeführt haben, müssen Sie Ihren phpMyAdmin überprüfen. Sie sehen nun Ihre beiden erstellten Tabellen.

Private und öffentliche Schlüssel

Der private und öffentliche Schlüssel jedes Benutzers ist einzigartig und wird zum Ver- und Entschlüsseln der Nachrichten verwendet. Wir verschlüsseln die Nachricht mit dem privaten Schlüssel des Absenders und dem öffentlichen Schlüssel des Empfängers. Ebenso entschlüsseln wir die Nachricht mit dem privaten Schlüssel des angemeldeten Benutzers und dem öffentlichen Schlüssel eines anderen Benutzers. Also werden wir ein Formular in unserem erstellen index.php Datei.

<form onsubmit="doLogin(this)">
  <input type="email" name="email" id="email" placeholder="Enter email" />
  <input type="submit" value="Login" />
</form>

Sie müssen die folgende Funktion in Ihrem eigenen Anmeldemodul ausführen. Wir gehen nicht darauf ein Authentifizierung denn das ist nicht Gegenstand dieses Tutorials. Wenn das Formular gesendet wird, rufen wir eine AJAX-Anfrage auf, um den Benutzer zu authentifizieren.

<script>
  function doLogin() {
    event.preventDefault()
    
    const form = event.target
    const formData = new FormData(form)
    
    const ajax = new XMLHttpRequest()
    ajax.open("POST", "login.php", true)
    ajax.onreadystatechange = function () {
      if (this.readyState == 4 && this.status == 200) {
        if (!this.responseText) {
          updateKeys()
        }
      }
    }

    ajax.send(formData)
  }
</script>

Erstellen Sie eine Datei mit dem Namen login.php Dadurch erfahren Sie, ob der angemeldete Benutzer über private und öffentliche Schlüssel verfügt.

<?php

require_once "db.php";
$email = $_POST["email"];

$sql = "SELECT publicKey FROM users WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute([
  $email
]);
$user = $result->fetchObject();

echo ($user && $user->publicKey != null);
exit();

Dies gibt „true“ oder „false“ zurück und gibt an, ob der Benutzer über einen öffentlichen Schlüssel in der Datenbank verfügt. Wenn nicht, ruft die Clientseite eine weitere AJAX-Anfrage von der Funktion auf updateKeys() um die Schlüssel zu generieren und in der Datenbank zu speichern.

async function updateKeys() {
      const keyPair = await window.crypto.subtle.generateKey(
          {
              name: "ECDH",
              namedCurve: "P-256",
          },
          true,
          ["deriveKey", "deriveBits"]
      )

      const publicKeyJwk = await window.crypto.subtle.exportKey(
          "jwk",
          keyPair.publicKey
      )

      const privateKeyJwk = await window.crypto.subtle.exportKey(
          "jwk",
          keyPair.privateKey
      )

      const formData = new FormData()
      formData.append("email", document.getElementById("email").value)
      formData.append("publicKey", JSON.stringify(publicKeyJwk))
      formData.append("privateKey", JSON.stringify(privateKeyJwk))

      const ajax = new XMLHttpRequest()
      ajax.open("POST", "update-keys.php", true)
      ajax.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200) {
          console.log(this.responseText)
        }
      }

      ajax.send(formData)
}

Wir verwenden den Kurvenalgorithmus P-256, um ein Schlüsselpaar zu generieren. Dann exportieren wir private und öffentliche Schlüssel JWK (JSON Web Token). Um sie in der Datenbank zu speichern, konvertieren wir sie in einen JSON-String. Jetzt müssen wir eine Datei mit dem Namen erstellen update-keys.php Dadurch werden diese Schlüssel in der Tabelle des Benutzers aktualisiert.

<?php

require_once "db.php";

$email = $_POST["email"];
$publicKey = $_POST["publicKey"];
$privateKey = $_POST["privateKey"];

$sql = "UPDATE users SET publicKey = ?, privateKey = ? WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute([
  $publicKey,
  $privateKey,
  $email
]);

echo "Updated";
exit();

Versuchen Sie, das auszuführen index.php Datei erneut. Geben Sie eine beliebige E-Mail-Adresse des Benutzers aus der Datenbank ein und klicken Sie auf „Anmelden“. In der Browserkonsole wird die Meldung „Aktualisiert“ angezeigt. Sie werden es jedoch nur einmal sehen, denn sobald die öffentlichen Schlüssel aktualisiert sind, wird diese Funktion nicht mehr aufgerufen. Wenn Sie Ihren phpMyAdmin überprüfen, werden Sie feststellen, dass der private und der öffentliche Schlüssel dieses Benutzers aktualisiert werden. Sie sollten dies für beide Benutzer tun, damit jeder Benutzer seinen eigenen privaten und öffentlichen Schlüssel hat.

Nachricht verschlüsseln

Da nun jeder Benutzer über seinen eigenen privaten und öffentlichen Schlüssel verfügt, können wir diese zum Verschlüsseln von Nachrichten und zum Speichern in der Datenbank verwenden. Erstellen Sie eine Datei mit dem Namen send.php Daraufhin wird ein Formular zur Eingabe der E-Mail-Adressen von Absender und Empfänger sowie einer zu verschlüsselnden Nachricht angezeigt.

<form onsubmit="sendMessage()" id="form-message">
  <input type="email" name="sender" placeholder="Sender email" />
  <input type="email" name="receiver" placeholder="Receiver email" />
  <textarea name="message" placeholder="Message"></textarea>
  <input type="submit" value="Send" />
</form>

Wir erstellen zwei Javascript-Variablen, die den privaten Schlüssel des Senders und die öffentlichen Schlüsselwerte des Empfängers enthalten.

<script>
  let publicKey = ""
  let privateKey = ""
</script>

Wir benutzen lassen da diese Werte später aktualisiert werden. Erstellen Sie eine Funktion, die aufgerufen wird, wenn das obige Formular gesendet wird.

function sendMessage() {
  event.preventDefault()

  if (publicKey == "" || privateKey == "") {
    const form = event.target
    const formData = new FormData(form)
    
    const ajax = new XMLHttpRequest()
    ajax.open("POST", "get-keys.php", true)
    ajax.onreadystatechange = function () {
      if (this.readyState == 4 && this.status == 200) {
        const response = JSON.parse(this.responseText)
        privateKey = JSON.parse(response[0])
        publicKey = JSON.parse(response[1])
        doSendMessage()
      }
    }

    ajax.send(formData)
  } else {
    doSendMessage()
  }
}

Dabei wird zunächst geprüft, ob die privaten und öffentlichen Schlüssel bereits abgerufen wurden. Wenn es abgerufen wird, wird es aufgerufen doSendMessage() Funktion, die wir später erstellen werden. Wenn nicht, holen wir zuerst die Schlüssel und rufen dann die zweite Funktion auf. Wir verwenden diese Prüfung, denn wenn Sie mehrere Nachrichten an denselben Empfänger senden, sollte dieser nicht bei jeder Anfrage zum Senden einer Nachricht private und öffentliche Schlüssel erhalten.

Jetzt erstellen wir eine Datei mit dem Namen get-keys.php um den privaten Schlüssel des Senders und den öffentlichen Schlüssel des Empfängers abzurufen.

<?php

require_once "db.php";

$sender = $_POST["sender"];
$receiver = $_POST["receiver"];

$sql = "SELECT privateKey FROM users WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute([
  $sender
]);
$userSender = $result->fetchObject();

$sql = "SELECT publicKey FROM users WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute([
  $receiver
]);
$userReceiver = $result->fetchObject();

echo json_encode([
  $userSender->privateKey,
  $userReceiver->publicKey
]);
exit();

Wenn die Schlüssel auf der Clientseite zurückgegeben werden, werden die Variablen aktualisiert und die zweite Funktion wird aufgerufen, um die Nachricht zu senden.

async function doSendMessage() {
      const form = document.getElementById("form-message")
      const formData = new FormData()
      formData.append("sender", form.sender.value)
      formData.append("receiver", form.receiver.value)

      const publicKeyObj = await window.crypto.subtle.importKey(
          "jwk",
          publicKey,
          {
              name: "ECDH",
              namedCurve: "P-256",
          },
          true,
          []
      )

      const privateKeyObj = await window.crypto.subtle.importKey(
          "jwk",
          privateKey,
          {
              name: "ECDH",
              namedCurve: "P-256",
          },
          true,
          ["deriveKey", "deriveBits"]
      )

      const derivedKey = await window.crypto.subtle.deriveKey(
          { name: "ECDH", public: publicKeyObj },
          privateKeyObj,
          { name: "AES-GCM", length: 256 },
          true,
          ["encrypt", "decrypt"]
      )

      const encodedText = new TextEncoder().encode(form.message.value)
      const iv = new TextEncoder().encode(new Date().getTime())
      const encryptedData = await window.crypto.subtle.encrypt(
          { name: "AES-GCM", iv: iv },
          derivedKey,
          encodedText
      )
      const uintArray = new Uint8Array(encryptedData)
      const string = String.fromCharCode.apply(null, uintArray)
      const base64Data = btoa(string)
      const b64encodedIv = btoa(new TextDecoder("utf8").decode(iv))

      formData.append("message", base64Data)
      formData.append("iv", b64encodedIv)
  
      const ajax = new XMLHttpRequest()
      ajax.open("POST", "send-message.php", true)
      ajax.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200) {
          console.log(this.responseText)
        }
      }

      ajax.send(formData)
}

Wir werden denselben P-256-Kurvenalgorithmus verwenden, um die privaten und öffentlichen Schlüssel zu importieren, die wir beim Exportieren verwendet haben. Dann erstellen wir einen abgeleiteten Schlüssel aus beiden (privaten und öffentlichen) Schlüsseln. Wir werden den abgeleiteten Schlüssel, IV und die verschlüsselte Nachricht verwenden, um die Nachricht zu verschlüsseln. Sobald die Nachricht verschlüsselt ist, konvertieren wir die verschlüsselte Nachricht und IV in eine Base64-Zeichenfolge und senden sie in der AJAX-Anfrage. IV wird zum Entschlüsseln der Nachricht verwendet. Dann erstellen wir eine Datei mit dem Namen send-message.php um die Daten in der Datenbank zu speichern.

<?php

require_once "db.php";

$sender = $_POST["sender"];
$receiver = $_POST["receiver"];
$message = $_POST["message"];
$iv = $_POST["iv"];

$sql = "INSERT INTO messages(sender, receiver, message, iv) VALUES (?, ?, ?, ?)";
$result = $conn->prepare($sql);
$result->execute([
  $sender,
  $receiver,
  $message,
  $iv
]);
echo $conn->lastInsertId();

Führen Sie die Datei aus send.php im Browser. Geben Sie die E-Mail-Adresse des Absenders und des Empfängers ein, geben Sie die Nachricht ein und klicken Sie auf „Senden“. Wenn alles gut geht, sehen Sie die eingefügte Nachrichten-ID in der Browserkonsole.

Nachricht entschlüsseln

Jetzt müssen wir die verschlüsselten Nachrichten entschlüsseln. Erstellen Sie eine Datei mit dem Namen read.php. Hier erstellen wir ein Formular, um die E-Mail-Adressen des Absenders und des Empfängers einzugeben, um deren Nachrichten abzurufen.

<form onsubmit="readMessages()" id="form-read">
  <input type="email" name="sender" placeholder="Sender email" />
  <input type="email" name="receiver" placeholder="Receiver email" />
  <input type="submit" value="Read" />
</form>

Dann erstellen wir eine Javascript-Funktion, die aufgerufen wird, wenn das obige Formular gesendet wird.

function readMessages() {
  event.preventDefault()

  const form = event.target
  const formData = new FormData(form)
  
  const ajax = new XMLHttpRequest()
  ajax.open("POST", "get-messages.php", true)
  ajax.onreadystatechange = async function () {
    if (this.readyState == 4 && this.status == 200) {
      const response = JSON.parse(this.responseText)

      const publicKeyObj = await window.crypto.subtle.importKey(
          "jwk",
          JSON.parse(response.publicKey),
          {
              name: "ECDH",
              namedCurve: "P-256",
          },
          true,
          []
      )

      const privateKeyObj = await window.crypto.subtle.importKey(
          "jwk",
          JSON.parse(response.privateKey),
          {
              name: "ECDH",
              namedCurve: "P-256",
          },
          true,
          ["deriveKey", "deriveBits"]
      )

      const derivedKey = await window.crypto.subtle.deriveKey(
          { name: "ECDH", public: publicKeyObj },
          privateKeyObj,
          { name: "AES-GCM", length: 256 },
          true,
          ["encrypt", "decrypt"]
      )

      for (let a = 0; a < response.messages.length; a++) {
        const iv = new Uint8Array(atob(response.messages[a].iv).split("").map(function(c) {
            return c.charCodeAt(0)
        }))
        const initializationVector = new Uint8Array(iv).buffer
        const string = atob(response.messages[a].message)
        const uintArray = new Uint8Array(
            [...string].map((char) => char.charCodeAt(0))
        )
        const decryptedData = await window.crypto.subtle.decrypt(
            {
                name: "AES-GCM",
                iv: initializationVector,
            },
            derivedKey,
            uintArray
        )
        const message = new TextDecoder().decode(decryptedData)
        console.log(message)
      }
    }
  }

  ajax.send(formData)
}

Dadurch wird eine AJAX-Anfrage aufgerufen, um die Nachrichten abzurufen. Die API gibt außerdem die privaten und öffentlichen Schlüssel zurück, die zum Entschlüsseln der Nachricht erforderlich sind. Derselbe Code kann zum Importieren der Schlüssel verwendet werden, die wir zum Senden der Nachricht verwendet haben. Erstellen Sie eine Datei mit dem Namen get-messages.php und schreibe den folgenden Code hinein.

<?php

require_once "db.php";

$sender = $_POST["sender"];
$receiver = $_POST["receiver"];

$sql = "SELECT * FROM messages WHERE sender = ? AND receiver = ?";
$result = $conn->prepare($sql);
$result->execute([
  $sender,
  $receiver
]);
$messages = $result->fetchAll(PDO::FETCH_OBJ);

$sql = "SELECT privateKey FROM users WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute([
  $sender
]);
$userSender = $result->fetchObject();

$sql = "SELECT publicKey FROM users WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute([
  $receiver
]);
$userReceiver = $result->fetchObject();

echo json_encode([
  "messages" => $messages,
  "privateKey" => $userSender->privateKey,
  "publicKey" => $userReceiver->publicKey
]);
exit();

Wenn Sie das ausführen read.php Nachdem Sie die Datei jetzt heruntergeladen haben, werden die entschlüsselten Nachrichten auf der Konsolenregisterkarte angezeigt. Wenn Sie jedoch die Registerkarte „Netzwerk“ des Browsers sehen, sehen Sie, dass die Nachrichten verschlüsselt vom Server zurückgegeben werden. Das bedeutet, dass Ihre Nachrichten online entschlüsselt werden, wenn sie auf der Client-Seite eintreffen. Somit sind sie während des Transports sicher.

Ende-zu-Ende-Verschlüsselung
Ende-zu-Ende-Verschlüsselung

So können Sie eine Ende-zu-Ende-Verschlüsselung in Javascript mit PHP und MySQL durchführen. In diesem Tutorial wurde keine externe Bibliothek verwendet, daher funktioniert der hier verwendete Code auf allen Frameworks.


Beitragsaufrufe: 59

[ad_2]