Was ist Steganographie? Stellen Sie sich vor, Sie möchten einem Freund eine geheime Nachricht schicken, aber der Kanal, den Sie verwenden möchten, ist kompromittiert und wird überwacht. Sie könnten eine Verschlüsselung verwenden, aber das würde den Verdacht der Leute erregen, die Ihre Gespräche überwachen, also müssten Sie etwas anderes verwenden. Heutzutage ist Steganografie eine Methode, um geheime Nachrichten in einer anderen, nicht geheimen Datei (z. B. einem Katzenbild) zu verstecken, sodass diese Datei, wenn Sie sie senden, nicht erkannt wird. Steganografie beschränkt sich nicht nur auf das Verstecken von Text in Bildern, sondern bedeutet im Allgemeinen „das Verstecken geheimer Informationen in einer anderen, nicht geheimen Nachricht oder einem physischen Objekt“: Sie können einige Nachrichten in Audio-, Video- oder anderen Texten verstecken, indem Sie beispielsweise eine Spaltentransposition verwenden. Auch in vielen anderen Fällen kann die Steganografie äußerst nützlich sein. Sie kann beispielsweise eine gute Alternative zum Wasserzeichen in vertraulichen Dokumenten sein, um diese vor dem Verlust der Kenntnis der Öffentlichkeit zu schützen. Es gibt viele Möglichkeiten, Informationen in Bildern zu verbergen, vom einfachen Anhängen des Textes an das Ende der Datei bis hin zum Verstecken in den Metadaten. In diesem Artikel möchte ich eine fortgeschrittenere Methode der Steganografie behandeln, die bis auf die binäre Ebene geht und Nachrichten innerhalb der Grenzen des Bildes selbst versteckt. Erstellen einer steganografischen Engine Die Benutzeroberfläche Für mein Beispiel zur Steganographie habe ich mich für JavaScript entschieden, da es eine leistungsstarke Programmiersprache ist, die in einem Browser ausgeführt werden kann. Ich habe eine einfache Schnittstelle entwickelt, die es Benutzern ermöglicht, ein Bild hochzuladen, um die darin versteckte Nachricht zu lesen oder selbst eine Nachricht in einem Bild zu verschlüsseln. <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Steganography</title> <style> body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; } textarea { width: 300px; height: 100px; } button { margin: 10px; } #outputImage { margin-top: 20px; max-width: 100%; } </style> </head> <body> <h1>Steganography Example</h1> <input type="file" id="upload" accept="image/*"><br> <canvas id="canvas" style="display:none;"></canvas><br> <textarea id="text" placeholder="Enter text to encode"></textarea><br> <button id="encode">Encode Text</button> <button id="decode">Decode Text</button> <p id="decodedText"></p> <img id="outputImage" alt="Output Image"> <script src="./script.js"></script> </body> </html> Um es zu verwenden, können Benutzer einfach ein Bild auswählen, das sie bearbeiten möchten, und entweder versuchen, einen Text daraus zu dekodieren, oder es kodieren und das Bild später herunterladen. Bilder verarbeiten Um mit Bildern in JavaScript zu arbeiten, können wir die verwenden. Sie bietet viele verschiedene Funktionen zum Bearbeiten und Zeichnen von Bildern, Animationen oder sogar Spielegrafiken und Videos. Canvas-API Die Canvas-API wird hauptsächlich für 2D-Grafiken verwendet. Wenn Sie mit 3D-Grafiken mit Hardwarebeschleunigung arbeiten möchten, können Sie die verwenden (die übrigens auch das <canvas>-Element verwendet). WebGL-API const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const image = new Image(); Um die Bilddatei aus dem Dateisystem zu lesen und sie dem Canvas-Kontext hinzuzufügen, können wir die verwenden. Sie ermöglicht es uns, den Inhalt jeder auf dem Computer des Benutzers gespeicherten Datei problemlos zu lesen, ohne dass eine benutzerdefinierte Bibliothek erforderlich ist. FileReader-API function handleFileUpload(event) { const reader = new FileReader(); reader.onload = function (e) { image.src = e.target.result; image.onload = function () { canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); }; }; reader.readAsDataURL(event.target.files[0]); } Es liest eine Datei und zeichnet das Bild in dieser Datei in unseren zuvor definierten 2D-Canvas-Kontext. Danach können wir entweder Text in dieses Bild kodieren oder versuchen, den Text aus dem Bild zu lesen. Text in Bildern ausblenden Bilder bestehen aus Pixeln und jedes Pixel enthält Informationen über seine Farben. Wenn ein Bild beispielsweise mit dem RGBA-Modell codiert wird, enthält jedes Pixel 4 Bytes an Informationen darüber, wie viel Rot, Grün, Blau und Alpha (Deckkraft) es darstellt. Um Text in einem Bild zu kodieren, könnten wir einen dieser Kanäle verwenden (zum Beispiel den Alphakanal). Da diese Informationen im Binärsystem dargestellt werden (wie 01001100), könnten wir das letzte Bit auf das ändern, was wir brauchen. Es wird als Least Significant Bit (LSB) bezeichnet und seine Änderung bewirkt nur minimale Änderungen am Bild selbst, sodass es von einem Menschen nicht mehr zu unterscheiden ist. Stellen Sie sich nun vor, wir haben einen Text wie „Hallo“ und möchten ihn in ein Bild kodieren. Der Algorithmus hierfür wäre Wandeln Sie den „Hallo“-Text in Binärcode um. Iterieren Sie durch die Bytes der Bilddaten und ersetzen Sie das LSB dieser Bytes durch ein Bit aus dem Binärtext (jedes Pixel enthält 4 Bytes Daten für jede der Farben. In meinem Beispiel möchte ich den Opazitätskanal des Bildes ändern, daher würde ich jedes 4. Byte iterieren). Fügen Sie am Ende der Nachricht ein hinzu, damit wir beim Dekodieren wissen, wann wir aufhören müssen. Nullbyte Wenden Sie geänderte Bildbytes auf das Bild selbst an. Zuerst müssen wir den Text, den wir kodieren möchten, vom Benutzer nehmen und einige grundlegende Validierungen daran durchführen. const text = document.getElementById("text").value; if (!text) { alert("Please enter some text to encode."); return; } Dann müssen wir den Text in Binärcode umwandeln und eine Leinwand des Bildes erstellen, in das wir diesen Text kodieren möchten. const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; for (let i = 0; i < text.length; i++) { let binaryChar = text.charCodeAt(i).toString(2).padStart(8, "0"); binaryText += binaryChar; } Dazu können wir einfach über jedes Zeichen iterieren und mithilfe der Funktion einen Unicode-Index abrufen. Dieser Unicode wird dann in Binärzahlen umgewandelt und aufgefüllt, sodass er dieselbe Länge wie jedes andere Zeichen hat. charCodeAt Beispielsweise wird der Buchstabe „H“ in Unicode als 72 dargestellt. Wir konvertieren diese Zahl dann in eine Binärzahl (1001000) und fügen am Anfang Nullen hinzu (01001000), um sicherzustellen, dass alle Buchstaben dieselbe Länge (8 Bit) haben. Dann müssen wir am Ende der Nachricht ein Null-Byte hinzufügen, um sicherzustellen, dass wir beim Entschlüsseln zwischen dem echten Text und den zufälligen Pixeldaten des Bildes unterscheiden können. binaryText += "00000000"; Anschließend müssen wir einige grundlegende Überprüfungen durchführen, um sicherzustellen, dass das Bild über genügend Pixel zum Kodieren unserer Nachricht verfügt, sodass es nicht zu einem Überlauf kommt. if (binaryText.length > data.length / 4) { alert("Text is too long to encode in this image."); return; } Und dann kommt der interessanteste Teil, die Kodierung der Nachricht. Das Datenarray, das wir zuvor definiert haben, enthält Pixelinformationen in Form von RGBA-Werten für jedes Pixel im Bild. Wenn das Bild also RGBA-kodiert ist, wird jedes Pixel darin durch 4 Werte des Datenarrays dargestellt; jeder Wert stellt dar, wie viel Rot, Grün und Blau dieses Pixel hat. for (let i = 0; i < binaryText.length; i++) { data[i * 4] = (data[i * 4] & 0b11111110) | parseInt(binaryText[i]); } ctx.putImageData(imgData, 0, 0); const outputImage = document.getElementById("outputImage"); outputImage.src = canvas.toDataURL(); Im obigen Code iterieren wir über unseren binär codierten Text. findet ein Byte, das wir ändern müssen, und da wir nur die Bytes eines bestimmten Kanals ändern möchten, multiplizieren wir die Variable mit um darauf zuzugreifen. data[i * 4] i 4 Die Operation setzt das niedrigstwertige Bit auf . Wenn beispielsweise binär ist, dann ergibt die Operation . Dadurch wird sichergestellt, dass das niedrigstwertige Bit auf gesetzt ist, bevor wir weitere Manipulationen damit vornehmen. data[i * 4] & 0b11111110 0 data[i * 4] 10101101 10101101 & 11111110 10101100 0 Das ist ein aktuelles Bit aus der binär codierten Zeichenfolge; es ist entweder oder . Wir können dieses Bit dann mit einer bitweisen ODER-Operation ( ) auf das LSB setzen. Wenn beispielsweise der linke Teil des bitweisen ODER ist und ist, dann würde ergeben. Wenn das aktuelle Bit wäre, würde das ODER ergeben. Aus diesem Grund mussten wir zunächst das LSB löschen. parseInt(binaryText[i]) 1 0 | 10101100 binaryText[i] 1 10101100 | 00000001 10101101 0 10101100 Sobald die Nachricht codiert ist, können wir sie in die aktuelle Leinwand einfügen und mit der Methode in HTML rendern. canvas.toDataURL Versteckte Botschaften aus Bildern entschlüsseln Der Prozess der Dekodierung eines Bildes ist eigentlich viel einfacher als die Kodierung. Da wir bereits wissen, dass wir nur den Alphakanal kodiert haben, können wir einfach jedes vierte Byte durchlaufen, das letzte Bit lesen, es zu unserem endgültigen String verketten und diese Daten von binär in einen Unicode-String umwandeln. Zuerst müssen wir die Variablen initialisieren. Da bereits mit den Bildinformationen gefüllt ist (wir rufen jedes Mal auf, wenn wir eine Datei aus dem Dateisystem lesen), können wir sie einfach in die Datenvariable extrahieren. imgData ctx.drawImage const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; let binaryText = ""; let decodedText = ""; Dann müssen wir über jedes 4. Byte des Bildes iterieren, das letzte Bit lesen und es mit der Variable verketten. binaryText for (let i = 0; i < data.length; i += 4) { binaryText += (data[i] & 1).toString(); } ist das codierte Byte, und um das LSB zu extrahieren, können wir den bitweisen AND-Operator ( ) verwenden. Er nimmt zwei Werte und führt eine AND-Operation an jedem Paar entsprechender Bits durch. Indem wir mit vergleichen, isolieren wir im Grunde das niederwertigste Bit aus den Pixelinformationen, und wenn das LSB ist, dann ist das Ergebnis einer solchen Operation Wenn das LSB ist, wäre das Ergebnis ebenfalls . data[i] & data[i] 1 1 1 0 0 Nachdem wir alle LSBs gelesen und in der Variable gespeichert haben, müssen wir sie von Binärtext in Klartext umwandeln. Da wir wissen, dass jedes Zeichen aus 8 Bits besteht (erinnern Sie sich, wie wir verwendet haben, um allen Zeichen die gleiche Länge zu geben?), können wir jedes 8. Zeichen der durchlaufen. binaryText padStart(8, "0") binaryText Dann können wir die Operation verwenden, um das aktuelle Byte aus dem basierend auf unserer Iteration zu extrahieren. Die Binärzeichenfolge kann mit der Funktion in eine Zahl umgewandelt werden. Dann können wir prüfen, ob das Ergebnis (ein Nullbyte) ist – wir stoppen die Umwandlung und fragen das Ergebnis ab. Andernfalls können wir herausfinden, welches Zeichen der Unicode-Zahl entspricht, und es unserer Ergebniszeichenfolge hinzufügen. .slice() binaryText parseInt(byte, 2) 0 for (let i = 0; i < binaryText.length; i += 8) { let byte = binaryText.slice(i, i + 8); if (byte.length < 8) break; // Stop if the byte is incomplete let charCode = parseInt(byte, 2); if (charCode === 0) break; // Stop if we hit a null character decodedText += String.fromCharCode(charCode); } Der dekodierte Text kann dann sicher einem Benutzer angezeigt werden: document.getElementById("decodedText").textContent = decodedText; Ich habe den vollständigen Code, der in diesem Artikel verwendet wurde, in meinem gelassen. Sie können gerne damit herumspielen. Es gibt viele Dinge, die verbessert werden könnten :) GitHub-Repository Abschließende Gedanken Steganographie ist eine sehr leistungsfähige Technik und kann in vielen verschiedenen Anwendungsfällen eingesetzt werden, angefangen bei der Dokumentenüberprüfung, der Verhinderung von Datenlecks, der KI-Bildüberprüfung, der DRM-Verwaltung von Musikdateien und vielem mehr. Diese Technik kann sogar auf Videos, Spiele oder sogar Rohtext angewendet werden, daher denke ich, dass sie ein enormes Potenzial hat. Im Zeitalter von NFTs und Blockchains ist es noch interessanter zu sehen, welche Anwendungsfälle es geben wird und wie sich diese Technik weiterentwickeln wird.