
🔐 Bildsteganografie in PHP: Text in Pixeln mit LSB verstecken
Click, und Ihre encoded.png sieht identisch aus wie container.jpg. Niemand wird vermuten, dass darin eine versteckte Nachricht steckt. Steganografie macht Daten nicht unlesbar wie Verschlüsselung, sondern verbirgt die Tatsache der Übertragung selbst. Für einen PHP-Entwickler ist das eine jener Techniken, die es wert sind, auf der Ebene von Bits und Pixeln verstanden zu werden.
LSB-Steganografie in PHP mit dem GD-Modul ist kein Produktionswerkzeug, sondern ein Übungsfeld. Ein Übungsfeld, das Ihnen den Umgang mit binärer Darstellung, Bitmasken und verlustfreien Formaten besser beibringt als ein Dutzend CRUD-Aufgaben. Sie gehen von der Frage „Wie ist ein Bild intern aufgebaut?" bis zur Zeile php decrypt.php, die hello aus Pixeln extrahiert, und verstehen, warum JPEG dafür ungeeignet ist.
💡 Kurzer Überblick:
- Wir zerlegen die Theorie: wie das niederwertigste Bit des Blaukanals eine Nachricht verbirgt und warum das Auge es nicht sieht
- Wir schreiben drei PHP-Dateien:
functions.php(Hilfsfunktionen),encrypt.php(Kodierung),decrypt.php(Extraktion) - Wir führen es mit einem echten JPEG-Container aus und erhalten ein visuell nicht unterscheidbares PNG-Ergebnis
- Wir gehen die Grenzen durch: Kompression, Erkennbarkeit, Kapazität und die Kombination mit AES-Verschlüsselung
- Wir schauen uns fertige PHP-Bibliotheken an und entscheiden, wann GD ausreicht und wann es Zeit für Python ist
Wie LSB-Steganografie funktioniert
LSB (Least Significant Bit) ist eine Methode, bei der die geheime Nachricht in die niederwertigsten Bits der Pixel geschrieben wird. Der Name ist wörtlich zu nehmen: Wir nehmen das niederwertigste Bit eines Farbkanals und ersetzen es durch ein Bit der Nachricht.
Ein digitales Bild ist ein Raster aus Pixeln. Jedes Pixel wird mit drei Bytes kodiert: Rot (R), Grün (G) und Blau (B). Die Kombination der drei Werte von 0 bis 255 bestimmt die endgültige Farbe. Das Ändern des niederwertigsten Bits verschiebt den Kanalwert um höchstens 1; aus RGB(100, 50, 200) wird beispielsweise RGB(100, 50, 201). Das menschliche Auge kann den Unterschied nicht erkennen.
Nehmen wir ein oranges Pixel:
- Rot:
11001001(201) - Grün:
11111000(248) - Blau:
00000011(3)
Das niederwertigste Bit des Blaukanals ist eins. Ersetzen Sie es durch ein Bit der geheimen Nachricht, und die Farbe bleibt visuell gleich. Indem wir Pixel sequenziell verarbeiten, schreiben wir die gesamte Nachricht Bit für Bit.
Angenommen, wir müssen die Sequenz 1101 verbergen. Wir nehmen 4 Pixel und ändern das niederwertigste Bit des Blaukanals:
Pixel | R | G | Original B | LSB ersetzt | Neues B |
|---|---|---|---|---|---|
1 |
|
|
| → |
|
2 |
|
|
| → |
|
3 |
|
|
| → |
|
4 |
|
|
| → |
|
Die Dekodierung ist der umgekehrte Prozess: Wir sammeln die niederwertigsten Bits des Blaukanals aus denselben vier Pixeln und erhalten 1101. Keine Magie, nur bitweise Operationen.
In der Praxis sind Sie nicht auf den Blaukanal allein beschränkt: Sie können alle drei (R, G, B) nutzen und so das Volumen der versteckten Daten verdreifachen. In diesem Tutorial arbeiten wir der Einfachheit halber nur mit Blau, damit leichter nachvollziehbar ist, was mit jedem Bit geschieht.
Schritt-für-Schritt-Implementierung in PHP
Sie benötigen PHP mit dem GD-Modul, das bei praktisch allen Hosting-Anbietern vorinstalliert ist. Der Code ist für PHP 8.0+ geschrieben und bis 8.4 getestet. Erstellen Sie drei Dateien in einem Ordner.
Schritt 1: Hilfsfunktionen, functions.php
Zwei Hilfsfunktionen: einen String in binäre Darstellung umwandeln und zurück.
1 <?php 2 3 function toBin(string $str): string 4 { 5 $result = ''; 6 $len = strlen($str); 7 8 while ($len--) { 9 $result = str_pad( 10 decbin(ord($str[$len])), 11 8, 12 '0', 13 STR_PAD_LEFT 14 ) . $result; 15 } 16 17 return $result; 18 } 19 20 function toString(string $binary): string 21 { 22 $result = ''; 23 foreach (str_split($binary, 8) as $byte) { 24 $result .= chr((int) base_convert($byte, 2, 10)); 25 } 26 27 return $result; 28 }
toBin() wandelt jedes Zeichen des Strings über decbin(ord(...)) in eine 8-Bit-Darstellung um und füllt links mit Nullen auf, um ein vollständiges Byte zu erhalten. toString() macht das Gegenteil: Es zerlegt den Binärstring in 8-Bit-Blöcke, wandelt jeden Block in eine Dezimalzahl um und erhält das Zeichen über chr().
Die blockweise Verarbeitung in toString() ist keine Marotte. Der in Tutorials verbreitete Ansatz mit pack('H*', base_convert($binary, 2, 16)) scheitert bei Nachrichten, die länger als 31 Bit sind: Die Funktion base_convert arbeitet mit Ganzzahlen, und wenn PHP_INT_MAX überschritten wird, liefert sie Datenmüll zurück. Die byteweise Aufteilung löst das Problem für Nachrichten beliebiger Länge.
Schritt 2: Kodierung, encrypt.php
Liest einen JPEG-Container, bettet die Nachricht in die niederwertigsten Bits des Blaukanals ein und speichert das Ergebnis als PNG. Warum nicht JPEG? Ein verlustfreies Format ist zwingend erforderlich: JPEG-Kompression zerstört die versteckten Bits, und die Nachricht wird zu Rauschen.
1 <?php 2 3 require_once 'functions.php'; 4 5 $message = 'hello'; 6 $binary = toBin($message); 7 $msgLen = strlen($binary); 8 9 $src = 'container.jpg'; 10 $img = imagecreatefromjpeg($src); 11 12 for ($x = 0; $x < $msgLen; $x++) { 13 $y = $x; 14 $rgb = imagecolorat($img, $x, $y); 15 16 $r = ($rgb >> 16) & 0xFF; 17 $g = ($rgb >> 8) & 0xFF; 18 $b = $rgb & 0xFF; 19 20 $blueBits = str_pad(decbin($b), 8, '0', STR_PAD_LEFT); 21 $blueBits[7] = $binary[$x]; 22 $newB = bindec($blueBits); 23 24 $newColor = imagecolorallocate($img, $r, $g, $newB); 25 imagesetpixel($img, $x, $y, $newColor); 26 } 27 28 imagepng($img, 'encoded.png'); 29 30 echo "Закодировано {$msgLen} бит — результат в encoded.png\n";
imagecolorat() ermittelt die Pixelfarbe an den Koordinaten (x, y). Die Bitverschiebungen >>16 und >>8 extrahieren R, G, B. Der Blaukanal wird in einen 8-Bit-String expandiert, das niederwertigste Bit (Index 7) wird durch das nächste Bit der Nachricht ersetzt, und die neue Farbe wird über imagesetpixel() geschrieben.
Die Nachricht wird diagonal kodiert (y = x), was das Nachvollziehen der Logik in einem kleinen Beispiel erleichtert. In einem echten Projekt iterieren Sie zeilenweise durch alle Pixel und fügen einen 32-Bit-Header mit der Nachrichtenlänge hinzu: Ohne diesen weiß der Dekodierer nicht, wo er aufhören soll.
Schritt 3: Dekodierung, decrypt.php
Extrahiert die versteckte Nachricht: geht dieselben Pixel durch und sammelt die niederwertigsten Bits des Blaukanals.
1 <?php 2 3 require_once 'functions.php'; 4 5 $src = 'encoded.png'; 6 $img = imagecreatefrompng($src); 7 8 $bits = ''; 9 10 for ($x = 0; $x < 40; $x++) { 11 $y = $x; 12 $rgb = imagecolorat($img, $x, $y); 13 14 $b = $rgb & 0xFF; 15 $blueBits = str_pad(decbin($b), 8, '0', STR_PAD_LEFT); 16 $bits .= $blueBits[7]; 17 } 18 19 echo toString($bits) . "\n";
Hier ist 40 eine fest kodierte Anzahl von Bits, genau 5 Bytes für das Wort hello. In einer Produktionsversion ersetzen Sie diese Zahl durch die Länge aus dem Nachrichten-Header, den Sie beim Kodieren in den ersten 32 Bits geschrieben haben.
Schritt 4: Ausführung und Überprüfung
Legen Sie container.jpg im Ordner mit den Skripten ab. Der Ablauf:
php encrypt.phpgibt in der Konsole aus: „Encoded 40 bits, result in encoded.png".php decrypt.phpgibt in der Konsole aus:hello.- Öffnen Sie
container.jpgundencoded.pngnebeneinander; es gibt keinen Unterschied. Sie sind visuell nicht zu unterscheiden.
Sie können Byte für Byte mit ImageMagick vergleichen: magick compare container.jpg encoded.png diff.png hebt Unterschiede in den niederwertigsten Bits hervor, die für das Auge praktisch unsichtbar sind. Der zuverlässigste Weg zur Überprüfung ist jedoch, decrypt.php auszuführen und die Nachricht zu lesen.
Grenzen der Methode
LSB-Steganografie ist ein Lernwerkzeug, kein kryptografischer Schutz. Folgendes müssen Sie bedenken, bevor Sie diesen Ansatz in einem echten Projekt verwenden:
Kompression zerstört Daten. JPEG berechnet 8×8-Pixelblöcke mit Quantisierung neu, und die niederwertigsten Bits überleben das nicht. Deshalb speichert
encrypt.phpdas Ergebnis strikt als PNG (verlustfrei). Ein Durchlauf durch JPEG-Kompression, und die versteckten Bits werden zu Rauschen. Alternative Formate: BMP, TIFF ohne Kompression.Erkennbarkeit. LSB-Befüllung hinterlässt statistische Artefakte: Das Histogramm des Blaukanals verändert sich auf vorhersagbare Weise. RS-Analyse und der χ²-Test erkennen eine versteckte Nachricht in Sekunden. Ohne Verschlüsselung ist LSB Verschleierung, kein Schutz: Das Vorhandensein einer Nachricht kann erkannt werden, selbst wenn der Inhalt nicht lesbar ist.
Datenvolumen. Jedes Pixel liefert ein Bit im Blaukanal. Ein Bild mit 1000×1000 Pixeln kann bis zu 125 KB versteckten Text aufnehmen; das ist die mathematische Grenze, die sich aus der Definition der LSB-Methode ergibt. In der Praxis ist das nutzbare Volumen nach der PNG-Kompression geringer, zudem gehen einige Bits für den Header mit der Länge drauf.
Keine Kryptografie. Steganografie ersetzt keine Verschlüsselung, sondern ergänzt sie. Die korrekte Pipeline: Nachricht verschlüsseln (AES-256), dann den Geheimtext im Bild verstecken. Dann enthüllt selbst das Erkennen des versteckten Kanals nicht den Inhalt.
PHP GD ist ein Basiswerkzeug. GD bietet Low-Level-Zugriff auf Pixel, und zum Lernen ist das mehr als ausreichend. Das Python-Ökosystem für Steganografie ist jedoch deutlich reichhaltiger: die
stegano-Bibliothek (LSB + Verschlüsselung),Stegano(eine Sammlung von Methoden) undsteghide(C++, aber es gibt Python-Bindings). In reinem PHP gibt es mehrere Bibliotheken:kzykhys/Steganography(eine minimale LSB-Implementierung für PNG),seba/steganography(eine moderne Bibliothek mit LSB-Manipulation) undpateon/steganography(PHP 8.5+ mit GD- und Imagick-Unterstützung). Es gibt jedoch Stand 2026 keine produktionsreifen Lösungen auf dem Niveau vonsteghidein PHP.
⁉️🤔 Häufig gestellte Fragen
Kann man Text direkt in JPEG verstecken?
Nein. JPEG-Kompression teilt das Bild in 8×8-Blöcke und wendet Quantisierung an; die niederwertigsten Bits überleben das nicht. Um Daten in JPEG zu verstecken, wird ein anderer Ansatz verwendet: Daten werden in DCT-Koeffizienten (Diskrete Kosinustransformation) versteckt, nicht in Pixeln. Algorithmen: F5, JSteg, OutGuess. Dafür gibt es praktisch keine fertigen PHP-Bibliotheken; einfacher ist die Nutzung von Python.
Wie unterscheidet sich LSB von Steganografie in Metadaten?
LSB verändert Pixeldaten; die Nachricht ist „im Inneren" des Bildes versteckt. Metadaten (EXIF, XMP, PNG-tEXt-Chunks) befinden sich in den Datei-Headern und können von jedem Eigenschaften-Betrachter gelesen werden. Metadaten sind einfacher zu extrahieren, überleben aber JPEG-Kompression, während LSB das nicht kann.
Wie kann ich überprüfen, ob encoded.png tatsächlich eine versteckte Nachricht enthält?
Visuell gar nicht. Vergleichen Sie die Dateien Byte für Byte:
magick compare container.jpg encoded.png diff.png(ImageMagick) zeigt Unterschiede in den niederwertigsten Bits. Sie sind für das Auge praktisch unsichtbar, aber das Dienstprogramm hebt die geänderten Pixel hervor. Der zuverlässigste Weg ist,php decrypt.phpauszuführen und die Nachricht zu lesen.
Wie sicher ist das insgesamt?
Ohne Verschlüsselung ist es nicht sicher. LSB ohne vorherige AES-Verschlüsselung ist Verschleierung, kein Schutz. Die Tatsache der versteckten Datenübertragung wird durch statistische Tests in Sekunden erkannt. Die Kombination „AES-Verschlüsselung + LSB-Verschleierung" bietet ein akzeptables Niveau für Pet-Projekte, aber nicht für sensible Daten.
Gibt es fertige PHP-Bibliotheken für Steganografie?
Ja, aber sie sind alle für Lehrzwecke oder leichtgewichtig.
kzykhys/Steganographyist eine minimale LSB-Implementierung, die nur mit PNG funktioniert.seba/steganographyist neuer, mit einer sauberen API zum Kodieren/Dekodieren von Text in Bildern.pateon/steganographyist für PHP 8.5+ und unterstützt sowohl GD als auch Imagick. Für ernsthafte Aufgaben lohnt der Blick Richtung Python:stegano(LSB + Verschlüsselung) undStegano(eine Sammlung von Methoden, inklusive eines Stego-Image-Generators).
Steganografie in PHP: eine Lernbasis oder ein fertiges Werkzeug?
LSB-Steganografie in PHP mit GD ist ein ausgezeichnetes Übungsfeld, um zu verstehen, wie Daten auf Pixelebene versteckt werden. Sie haben einen funktionierenden Encode/Decode-Zyklus geschrieben, bitweise Operationen durchdrungen und in der Praxis gesehen, warum eine „unsichtbare" Änderung Spuren in der Bildstatistik hinterlässt.
Für eine Lerndemonstration, ein Pet-Projekt oder eine CTF-Herausforderung ist dieser Werkzeugkasten mehr als ausreichend. Für echten Datenschutz fügen Sie vor dem Verstecken Verschlüsselung hinzu und schauen sich Python-Bibliotheken an, die den PHP-Pendants in Fähigkeiten und Erkennungsresistenz um Längen voraus sind.
Wenn Sie tiefer einsteigen möchten, implementieren Sie die Kodierung in allen drei Farbkanälen gleichzeitig und fügen Sie einen 32-Bit-Header mit der Nachrichtenlänge hinzu. Sie erhalten ein Mini-Tool, das dreimal effizienter ist als die Basisversion und bereits als Kursprojekt taugt. Stellen Sie den Quellcode auf GitHub bereit, damit andere von Ihrem Beispiel lernen können.



