Skip to content

Alt om WordPress, webutvikling — og mer til

🔐 Bildesteganografi i PHP: skjule tekst i piksler med LSB

🔐 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

11001100

10010001

00101011

1

00101011

2

00011000

11110000

11111110

1

11111111

3

11100010

00100101

01010101

0

01010100

4

11111101

00001010

01000011

1

01000011

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
3function 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
20function 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
3require_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
12for ($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
28imagepng($img, 'encoded.png');
29
30echo "Закодировано {$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
3require_once 'functions.php';
4
5$src = 'encoded.png';
6$img = imagecreatefrompng($src);
7
8$bits = '';
9
10for ($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
19echo 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.php gir ut i konsollen: «Encoded 40 bits, result in encoded.png».
  • php decrypt.php gir ut i konsollen: hello.
  • Åpne container.jpg og encoded.png side 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.php resultatet 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) og steghide (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) og pateon/steganography (PHP 8.5+ med støtte for GD og Imagick). Men det finnes ingen produksjonsklare løsninger på nivå med steghide i 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øre php decrypt.php og 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/Steganography er en minimal LSB-implementasjon som kun fungerer med PNG. seba/steganography er nyere, med et rent API for koding/dekoding av tekst i bilder. pateon/steganography er PHP 8.5+ og støtter både GD og Imagick. For seriøse oppgaver, se mot Python: stegano (LSB + kryptering) og Stegano (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.