
🔐 Bildesteganografi i PHP: skjule tekst i piksler med LSB
Klikk, og encoded.png ser identisk ut med container.jpg. Ingen vil mistenke at det finnes en skjult melding inni. Steganografi gjør ikke data uleselige slik kryptering gjør; det skjuler selve det faktum at overføring finner sted. For en PHP-utvikler er dette en av de teknikkene det er verdt å forstå på bit- og pikselnivå.
LSB-steganografi i PHP med GD-modulen er ikke et produksjonsverktøy, men en treningsarena. En treningsarena som lærer deg å jobbe med binær representasjon, bitmasker og tapsfrie formater bedre enn et dusin CRUD-oppgaver. Du går fra «hvordan er et bilde bygget opp inni» til linjen php decrypt.php som henter ut hello fra piksler, og du forstår hvorfor JPEG ikke egner seg til dette.
💡 Rask oversikt:
- Vi bryter ned teorien: hvordan den minst signifikante biten i den blå kanalen skjuler en melding, og hvorfor øyet ikke ser det
- Vi skriver tre PHP-filer:
functions.php(hjelpefunksjoner),encrypt.php(koding),decrypt.php(uthenting) - Vi kjører det på en ekte JPEG-beholder og får et visuelt identisk PNG-resultat
- Vi går gjennom begrensningene: komprimering, detekterbarhet, kapasitet og kombinasjonen med AES-kryptering
- Vi ser på ferdige PHP-biblioteker og vurderer når GD er nok, og når det er på tide å bruke Python
Slik fungerer LSB-steganografi
LSB (Least Significant Bit) er en metode der den hemmelige meldingen skrives inn i de minst signifikante bitene til piksler. Navnet er bokstavelig: vi tar den minst signifikante biten i en fargekanal og erstatter den med en bit av meldingen.
Et digitalt bilde er et rutenett av piksler. Hver piksel er kodet med tre byte: rød (R), grønn (G) og blå (B). Kombinasjonen av tre verdier fra 0 til 255 bestemmer den endelige fargen. Å endre den minst signifikante biten forskyver kanalverdien med maksimalt 1; for eksempel blir RGB(100, 50, 200) til RGB(100, 50, 201). Det menneskelige øyet kan ikke skjelne forskjellen.
La oss ta en oransje piksel:
- Rød:
11001001(201) - Grønn:
11111000(248) - Blå:
00000011(3)
Den minst signifikante biten i den blå kanalen er én. Erstatt den med en bit av den hemmelige meldingen, så forblir fargen visuelt den samme. Ved å behandle piksler sekvensielt skriver vi hele meldingen bit for bit.
Anta at vi må skjule sekvensen 1101. Vi tar 4 piksler og endrer den minst signifikante biten i den blå kanalen:
Piksel | R | G | Opprinnelig B | LSB erstattet | Ny B |
|---|---|---|---|---|---|
1 |
|
|
| → |
|
2 |
|
|
| → |
|
3 |
|
|
| → |
|
4 |
|
|
| → |
|
Dekoding er den omvendte prosessen: vi samler de minst signifikante bitene fra den blå kanalen fra de samme fire piksler og får 1101. Ingen magi, bare bitvise operasjoner.
I praksis er du ikke begrenset til bare den blå kanalen: du kan bruke alle tre (R, G, B), noe som tredobler volumet av skjulte data. I denne veiledningen jobber vi kun med blå for enkelhets skyld, slik at det er lettere å se hva som skjer med hver bit.
Trinnvis implementering i PHP
Du trenger PHP med GD-modulen, som er forhåndsinstallert på praktisk talt alle hostingleverandører. Koden er skrevet for PHP 8.0+ og testet opp til 8.4. Opprett tre filer i én mappe.
Trinn 1: Hjelpefunksjoner, functions.php
To hjelpefunksjoner: konvertering av en streng til binær representasjon og tilbake.
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() konverterer hvert tegn i strengen til en 8-bits representasjon via decbin(ord(...)) og fyller på med nuller til venstre for å få en full byte. toString() gjør det motsatte: den deler den binære strengen i 8-bits biter, konverterer hver bit til et desimaltall og henter tegnet via chr().
Blokkvis behandling i toString() er ikke et innfall. Den populære tilnærmingen i veiledninger som bruker pack('H*', base_convert($binary, 2, 16)) feiler på meldinger lengre enn 31 bits: funksjonen base_convert opererer på heltall, og når den overskrider PHP_INT_MAX, returnerer den søppel. Oppdeling i byte løser problemet for meldinger av enhver lengde.
Trinn 2: Koding, encrypt.php
Leser en JPEG-beholder, bygger inn meldingen i de minst signifikante bitene i den blå kanalen og lagrer resultatet som PNG. Hvorfor ikke JPEG? Et tapsfritt format er obligatorisk: JPEG-komprimering vil ødelegge de skjulte bitene, og meldingen blir til støy.
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() henter pikselfargen ved koordinatene (x, y). Bitskiftene >>16 og >>8 trekker ut R, G, B. Den blå kanalen utvides til en 8-bits streng, den minst signifikante biten (indeks 7) erstattes med neste bit av meldingen, og den nye fargen skrives via imagesetpixel().
Meldingen kodes diagonalt (y = x), noe som gjør det lettere å følge logikken i et lite eksempel. I et ekte prosjekt itererer du gjennom alle piksler rad for rad og legger til en 32-bits header med meldingslengden: uten den vet ikke dekoderen hvor den skal stoppe.
Trinn 3: Dekoding, decrypt.php
Henter ut den skjulte meldingen: går gjennom de samme piksler og samler de minst signifikante bitene i den blå kanalen.
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";
Her er 40 et hardkodet antall bits, nøyaktig 5 byte for ordet hello. I en produksjonsversjon erstatter du dette tallet med lengden fra meldingsheaderen som du skrev i de første 32 bitene under kodingen.
Trinn 4: Kjøring og verifisering
Plasser container.jpg i mappen med skriptene. Fremgangsmåten:
php encrypt.phpgir ut i konsollen: «Encoded 40 bits, result in encoded.png».php decrypt.phpgir ut i konsollen:hello.- Åpne
container.jpgogencoded.pngside om side; det er ingen forskjell. De er visuelt identiske.
Du kan sammenligne byte for byte ved hjelp av ImageMagick: magick compare container.jpg encoded.png diff.png vil fremheve forskjeller i de minst signifikante bitene, praktisk talt usynlige for øyet. Men den mest pålitelige måten å verifisere på er å kjøre decrypt.php og lese meldingen.
Metodens begrensninger
LSB-steganografi er et læringsverktøy, ikke kryptografisk beskyttelse. Her er det du må ha i bakhodet før du bruker denne tilnærmingen i et ekte prosjekt:
Komprimering ødelegger data. JPEG omberegner 8×8 pikselblokker med kvantisering, og de minst signifikante bitene overlever ikke. Derfor lagrer
encrypt.phpresultatet utelukkende som PNG (tapsfritt). Én runde med JPEG-komprimering, og de skjulte bitene blir til støy. Alternative formater: BMP, TIFF uten komprimering.Detekterbarhet. LSB-utfylling etterlater statistiske artefakter: histogrammet til den blå kanalen endrer seg på en forutsigbar måte. RS-analyse og χ²-testen oppdager en skjult melding i løpet av sekunder. Uten kryptering er LSB skjuling, ikke beskyttelse: tilstedeværelsen av en melding kan oppdages selv om innholdet ikke kan leses.
Datavolum. Hver piksel gir fra seg én bit i den blå kanalen. Et bilde på 1000×1000 piksler kan romme opptil 125 KB skjult tekst; dette er den matematiske grensen som følger av definisjonen av LSB-metoden. I praksis, etter PNG-komprimering, er det nyttige volumet mindre, pluss at noen bits går til headeren med lengden.
Ikke kryptografi. Steganografi erstatter ikke kryptering; det kompletterer den. Den korrekte pipelinen: krypter meldingen (AES-256), skjul deretter chifferteksten i bildet. Da vil selv deteksjon av den skjulte kanalen ikke avsløre innholdet.
PHP GD er et grunnleggende verktøy. GD gir lavnivåtilgang til piksler, og for læring er dette mer enn nok. Men Python-økosystemet for steganografi er merkbart rikere:
stegano-biblioteket (LSB + kryptering),Stegano(et sett med metoder) ogsteghide(C++, men det finnes Python-bindinger). I ren PHP finnes det flere biblioteker:kzykhys/Steganography(en minimal LSB-implementasjon for PNG),seba/steganography(et moderne bibliotek med LSB-manipulasjon) ogpateon/steganography(PHP 8.5+ med støtte for GD og Imagick). Men det finnes ingen produksjonsklare løsninger på nivå medsteghidei PHP per 2026.
⁉️🤔 Ofte stilte spørsmål
Kan man skjule tekst direkte i JPEG?
Nei. JPEG-komprimering deler bildet inn i 8×8 blokker og anvender kvantisering; de minst signifikante bitene overlever ikke. For å skjule data i JPEG brukes en annen tilnærming: data skjules i diskrete cosinustransformasjonskoeffisienter (DCT), ikke i piksler. Algoritmer: F5, JSteg, OutGuess. Det finnes praktisk talt ingen ferdige PHP-biblioteker for dette; det er enklere å bruke Python.
Hvordan skiller LSB seg fra steganografi i metadata?
LSB endrer pikseldata; meldingen er skjult «inni» bildet. Metadata (EXIF, XMP, PNG tEXt-chunks) ligger i filheadere og kan leses av enhver egenskapsviser. Metadata er lettere å hente ut, men det overlever JPEG-komprimering, noe LSB ikke kan.
Hvordan kan jeg verifisere at encoded.png faktisk inneholder en skjult melding?
Visuelt kan du ikke det. Sammenlign filene byte for byte:
magick compare container.jpg encoded.png diff.png(ImageMagick) vil vise forskjeller i de minst signifikante bitene. De er praktisk talt usynlige for øyet, men verktøyet vil fremheve de endrede piksler. Den mest pålitelige måten er å kjørephp decrypt.phpog lese meldingen.
Hvor sikkert er dette totalt sett?
Uten kryptering er det ikke sikkert. LSB uten forutgående AES-kryptering er skjuling, ikke beskyttelse. Det faktum at skjult dataoverføring finner sted, oppdages av statistiske tester i løpet av sekunder. Kombinasjonen «AES-kryptering + LSB-skjuling» gir et akseptabelt nivå for hobbyprosjekter, men ikke for sensitive data.
Finnes det ferdige PHP-biblioteker for steganografi?
Ja, men de er alle pedagogiske eller lette.
kzykhys/Steganographyer en minimal LSB-implementasjon som kun fungerer med PNG.seba/steganographyer nyere, med et rent API for koding/dekoding av tekst i bilder.pateon/steganographyer PHP 8.5+ og støtter både GD og Imagick. For seriøse oppgaver, se mot Python:stegano(LSB + kryptering) ogStegano(et sett med metoder, inkludert en stego-bildegenerator).
Steganografi i PHP: en læringsbase eller et ferdig verktøy?
LSB-steganografi i PHP med GD er en utmerket treningsarena for å forstå hvordan data skjules på pikselnivå. Du skrev en fungerende kode/dekode-syklus, fant ut av bitvise operasjoner og så i praksis hvorfor en «usynlig» endring etterlater spor i bildestatistikken.
For en læringsdemonstrasjon, et hobbyprosjekt eller en CTF-utfordring er dette verktøysettet mer enn nok. For ekte databeskyttelse, legg til kryptering før skjuling og se på Python-biblioteker, som er hode og skuldre over PHP-motstykkene i kapasitet og motstand mot deteksjon.
Hvis du vil grave dypere, implementer koding i alle tre fargekanaler samtidig og legg til en 32-bits header med meldingslengden. Du får et miniverktøy som er 3 ganger mer effektivt enn grunnversjonen og allerede kvalifiserer som et kursprosjekt. Legg ut kildekoden på GitHub slik at andre kan lære av ditt eksempel.



