Skip to content

Kõik WordPressist, veebiarendusest — ja mitte ainult

🔐 Pildi steganograafia PHP-s: teksti peitmine pikslitesse LSB abil

🔐 Pildi steganograafia PHP-s: teksti peitmine pikslitesse LSB abil

Klõpsa ja sinu encoded.png näeb välja identselt container.jpg-ga. Keegi ei kahtlusta, et sees on peidetud sõnum. Steganograafia ei muuda andmeid loetamatuks nagu krüpteerimine, vaid peidab edastamise fakti enda. PHP arendaja jaoks on see üks neid tehnikaid, mida tasub mõista bittide ja pikslite tasandil.

LSB-steganograafia PHP-s koos GD-mooduliga ei ole tootmisvahend, vaid treeningplats. Treeningplats, mis õpetab sind töötama binaaresituse, bitimaskide ja kadudeta vormingutega paremini kui kümmekond CRUD-ülesannet. Sa jõuad küsimusest „kuidas pilt seestpoolt üles on ehitatud" reani php decrypt.php, mis tõmbab pikslitest välja hello, ja saad aru, miks JPEG selleks ei sobi.

💡 Kiire ülevaade:

  • Võtame lahti teooria: kuidas sinise kanali vähim tähtis bitt peidab sõnumit ja miks silm seda ei näe
  • Kirjutame kolm PHP-faili: functions.php (utiliidid), encrypt.php (kodeerimine), decrypt.php (väljavõtt)
  • Käivitame selle päris JPEG-konteineril ja saame visuaalselt eristamatu PNG-tulemuse
  • Vaatame läbi piirangud: tihendamine, tuvastatavus, maht ja kombineerimine AES-krüpteeringuga
  • Vaatame valmis PHP-teeke ja otsustame, millal GD-st piisab ja millal on aeg kasutada Pythonit

Kuidas LSB-steganograafia töötab

LSB (Least Significant Bit) on meetod, kus salasõnum kirjutatakse pikslite vähim tähtsatesse bittidesse. Nimi on sõna-sõnaline: võtame värvikanali vähim tähtsa biti ja asendame selle sõnumi bitiga.

Digitaalne pilt on pikslivõre. Iga piksel on kodeeritud kolme baidiga: punane (R), roheline (G) ja sinine (B). Kolme väärtuse kombinatsioon vahemikus 0 kuni 255 määrab lõpliku värvi. Vähim tähtsa biti muutmine nihutab kanali väärtust maksimaalselt 1 võrra; näiteks RGB(100, 50, 200) muutub RGB(100, 50, 201)-ks. Inimsilm ei suuda erinevust eristada.

Võtame oranži piksli:

  • Punane: 11001001 (201)
  • Roheline: 11111000 (248)
  • Sinine: 00000011 (3)

Sinise kanali vähim tähtis bitt on üks. Asenda see salasõnumi bitiga ja värv jääb visuaalselt samaks. Piksleid järjest töödeldes kirjutame kogu sõnumi biti haaval.

Oletame, et peame peitma jada 1101. Võtame 4 pikslit ja muudame sinise kanali vähim tähtsat bitti:

Piksel

R

G

Algne B

LSB asendatud

Uus 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

Dekodeerimine on vastupidine protsess: kogume samast neljast pikslist sinise kanali vähim tähtsad bitid ja saame 1101. Ei mingit maagiat, ainult bitiopratsioonid.

Praktikas ei piirdu sa ainult sinise kanaliga: saad kasutada kõiki kolme (R, G, B), kolmekordistades peidetud andmete mahtu. Selles õpetuses töötame lihtsuse mõttes ainult sinisega, nii on lihtsam näha, mis iga bitiga toimub.

Samm-sammuline teostus PHP-s

Sul on vaja PHP-d koos GD-mooduliga, mis on eelinstallitud praktiliselt kõigil hostinguteenuse pakkujatel. Kood on kirjutatud PHP 8.0+ jaoks ja testitud kuni versioonini 8.4. Loo ühte kausta kolm faili.

Samm 1: Abifunktsioonid, functions.php

Kaks utiliiti: sõne teisendamine binaaresitusse ja tagasi.

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() teisendab sõne iga märgi 8-bitiseks esituseks decbin(ord(...)) abil ja täidab vasakult nullidega, et saada täisbait. toString() teeb vastupidist: tükeldab binaarsõne 8-bitisteks juppideks, teisendab iga jupi kümnendarvuks ja saab märgi chr() kaudu.

Plokkhaaval töötlemine toString()-s ei ole kapriis. Õpetustes levinud lähenemine pack('H*', base_convert($binary, 2, 16)) abil puruneb pikematel kui 31-bitistel sõnumitel: funktsioon base_convert opereerib täisarvudega ja PHP_INT_MAX ületamisel tagastab prügi. Baitideks jagamine lahendab probleemi igasuguse pikkusega sõnumite puhul.

Samm 2: Kodeerimine, encrypt.php

Loeb JPEG-konteineri, põimib sõnumi sinise kanali vähim tähtsatesse bittidesse ja salvestab tulemuse PNG-na. Miks mitte JPEG-na? Kadudeta vorming on kohustuslik: JPEG-tihendus hävitab peidetud bitid ja sõnum muutub müraks.

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() saab piksli värvi koordinaatidel (x, y). Bitinihked >>16 ja >>8 eraldavad R, G, B. Sinine kanal laiendatakse 8-bitiseks sõneks, vähim tähtis bitt (indeks 7) asendatakse sõnumi järgmise bitiga ja uus värv kirjutatakse imagesetpixel() abil.

Sõnum kodeeritakse diagonaalselt (y = x), mis teeb loogika jälgimise väikeses näites lihtsamaks. Päris projektis itereeri läbi kõik pikslid rida-realt ja lisa 32-bitine päis sõnumi pikkusega: ilma selleta ei tea dekooder, kus peatuda.

Samm 3: Dekodeerimine, decrypt.php

Tõmbab peidetud sõnumi välja: läbib samad pikslid ja kogub sinise kanali vähim tähtsad bitid.

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";

Siin on 40 koodi sisse kirjutatud bittide arv, täpselt 5 baiti sõna hello jaoks. Tootmisversioonis asenda see arv sõnumi päisest pärineva pikkusega, mille sa kodeerimisel esimesse 32 bitti kirjutasid.

Samm 4: Käivitamine ja kontroll

Aseta container.jpg skriptidega samasse kausta. Protseduur:

  • php encrypt.php väljastab konsooli: "Encoded 40 bits, result in encoded.png".
  • php decrypt.php väljastab konsooli: hello.
  • Ava container.jpg ja encoded.png kõrvuti; erinevust pole. Need on visuaalselt eristamatud.

Saad võrrelda bait baidi haaval, kasutades ImageMagicki: magick compare container.jpg encoded.png diff.png toob esile erinevused vähim tähtsates bittides, mis on silmale praktiliselt nähtamatud. Kuid kõige usaldusväärsem viis kontrollimiseks on käivitada decrypt.php ja lugeda sõnumit.

Meetodi piirangud

LSB-steganograafia on õppevahend, mitte krüptograafiline kaitse. Siin on, mida pead silmas pidama, enne kui seda lähenemist päris projektis kasutad:

  • Tihendamine hävitab andmed. JPEG arvutab 8×8 piksli plokid ümber koos kvantimisega ja vähim tähtsad bitid ei jää ellu. Seetõttu salvestab encrypt.php tulemuse rangelt PNG-na (kadudeta). Üks läbimine JPEG-tihendusest ja peidetud bitid muutuvad müraks. Alternatiivsed vormingud: BMP, tihendamata TIFF.

  • Tuvastatavus. LSB-täitmine jätab statistilisi artefakte: sinise kanali histogramm muutub ennustataval viisil. RS-analüüs ja χ²-test tuvastavad peidetud sõnumi sekunditega. Ilma krüpteeringuta on LSB varjamine, mitte kaitse: sõnumi olemasolu saab tuvastada isegi siis, kui sisu ei saa lugeda.

  • Andmemaht. Iga piksel annab sinises kanalis ühe biti. 1000×1000 piksline pilt mahutab kuni 125 KB peidetud teksti; see on matemaatiline piir, mis tuleneb LSB-meetodi definitsioonist. Praktikas on pärast PNG-tihendust kasulik maht väiksem, lisaks kulub osa bitte pikkusega päisele.

  • Mitte krüptograafia. Steganograafia ei asenda krüpteerimist, vaid täiendab seda. Õige töövoog: krüpti sõnum (AES-256), seejärel peida šifreeritud tekst pilti. Siis ei paljasta isegi peidetud kanali tuvastamine sisu.

  • PHP GD on baastööriist. GD pakub madalatasemelist ligipääsu pikslitele ja õppimiseks on see enam kui piisav. Kuid Pythoni ökosüsteem steganograafia jaoks on märgatavalt rikkalikum: stegano teek (LSB + krüpteering), Stegano (meetodite komplekt) ja steghide (C++, kuid olemas on Pythoni sidemed). Puhtas PHP-s on mitu teeki: kzykhys/Steganography (minimaalne LSB-teostus PNG jaoks), seba/steganography (kaasaegne teek LSB-manipulatsiooniga) ja pateon/steganography (PHP 8.5+ GD ja Imagicki toega). Kuid steghide'i tasemel tootmisvalmis lahendusi PHP-s 2026. aasta seisuga ei ole.

⁉️🤔 Korduma kippuvad küsimused

Kas teksti saab otse JPEG-sse peita?

Ei. JPEG-tihendus jagab pildi 8×8 plokkideks ja rakendab kvantimist; vähim tähtsad bitid ei jää ellu. Andmete peitmiseks JPEG-sse kasutatakse teistsugust lähenemist: andmed peidetakse diskreetse koosinusteisenduse (DCT) koefitsientidesse, mitte pikslitesse. Algoritmid: F5, JSteg, OutGuess. Selle jaoks praktiliselt puuduvad valmis PHP-teegid; lihtsam on kasutada Pythonit.

Kuidas erineb LSB steganograafiast metaandmetes?

LSB muudab piksliandmeid; sõnum on peidetud pildi „sisse". Metaandmed (EXIF, XMP, PNG tEXt tükid) asuvad faili päistes ja neid saab lugeda iga atribuutide vaaturiga. Metaandmeid on lihtsam välja võtta, kuid need elavad JPEG-tihenduse üle, samas kui LSB mitte.

Kuidas ma saan kontrollida, et encoded.png tõesti sisaldab peidetud sõnumit?

Visuaalselt ei saa. Võrdle faile bait baidi haaval: magick compare container.jpg encoded.png diff.png (ImageMagick) näitab erinevusi vähim tähtsates bittides. Need on silmale praktiliselt nähtamatud, kuid utiliit tõstab muutunud pikslid esile. Kõige usaldusväärsem viis on käivitada php decrypt.php ja lugeda sõnumit.

Kui turvaline see üldiselt on?

Ilma krüpteeringuta ei ole see turvaline. LSB ilma eelneva AES-krüpteeringuta on varjamine, mitte kaitse. Peidetud andmeedastuse fakti tuvastatakse statistiliste testidega sekunditega. Kombinatsioon „AES-krüpteering + LSB-varjamine" pakub lemmikloomaprojektide jaoks vastuvõetavat taset, kuid mitte tundlike andmete jaoks.

Kas PHP jaoks on olemas valmis steganograafia-teeke?

Jah, kuid need on kõik hariduslikud või kerged. kzykhys/Steganography on minimaalne LSB-teostus, mis töötab ainult PNG-ga. seba/steganography on uuem, puhta API-ga teksti kodeerimiseks/dekodeerimiseks piltides. pateon/steganography on PHP 8.5+ ja toetab nii GD-d kui Imagicki. Tõsiste ülesannete jaoks vaata Pythoni poole: stegano (LSB + krüpteering) ja Stegano (meetodite komplekt, sealhulgas stego-pildi generaator).

Steganograafia PHP-s: õppebaas või valmis tööriist?

LSB-steganograafia PHP-s koos GD-ga on suurepärane treeningplats mõistmaks, kuidas andmeid pikslitasandil peidetakse. Sa kirjutasid töötava kodeerimise/dekodeerimise tsükli, said selgeks bitiopratsioonid ja nägid praktikas, miks „nähtamatu" muutus jätab pildi statistikasse jäljed.

Õppedemonstratsiooniks, lemmikloomaprojektiks või CTF-väljakutseks on see tööriistakast enam kui piisav. Päris andmekaitse jaoks lisa enne varjamist krüpteering ja vaata Pythoni teeke, mis on võimekuse ja tuvastamiskindluse poolest PHP vastetest peajagu üle.

Kui tahad sügavamale kaevuda, teosta kodeerimine kõigis kolmes värvikanalis korraga ja lisa 32-bitine päis sõnumi pikkusega. Saad minitööriista, mis on 3 korda tõhusam kui baasversioon ja kvalifitseerub juba kursuseprojektiks. Postita lähtekood GitHubi, et teised saaksid sinu näitest õppida.