Ukrywanie tekstu w rysunku

Spoko_ws

W tym artykule zajmiemy się działem pokrewnym do kryptografii, czyli steganografią. O ile kryptografia stara się ukryć treść przekazywanej informacji to steganografia stara się ukryć sam fakt jej przesłania. Znacznie upraszczając, jeśli ktoś dostanie dokument po działaniu kryptografii, nie będzie mógł odczytać informacji, a po działaniu steganografii - nie będzie wiedział, że jakakolwiek (dodatkowa) informacja jest przesyłana.

Pisząc prosty programik wykorzystamy pewną niedoskonałość ludzkiego oka dotyczącą postrzegania barw. Ludzkie oko - teoretycznie - może zobaczyć nieograniczoną ilość barw, ale w rzeczywistości, spróbujcie namalować dwa przylegające prostokąty, jeden z barwą RGB: 100,100,100, a drugi - 101,101,101. Różnica jest praktycznie niedostrzegalna, a jeśli nawet, to dopiero przy ogromnym powiększeniu. W sprytny sposób to wykorzystamy.

Do czego dążę? Myśląc "ukrywanie tekstu w rysunku" można zazwyczaj wyobrazić sobie np. doklejanie kawałka zaszyfrowanego tekstu do pliku .bmp lub ukrywanie literek jako, dajmy na to, znaku wodnego. Niestety, z metod nie jest pewna, bo można zauważyć podejrzane powiększenie się pliku rysunku; druga natomiast sprawia spore problemy przy odczytaniu. W oparciu o poprzedni akapit, rysuje się jednak metoda trzecia - ukrywanie tekstu poprzez zmiany kolorów kolejnych piksli. Przejdźmy do konkretów.

Załóżmy, że chcemy ukryć w jakimś rysunku literkę "A" o kodzie ASCII 65. A więc po kolei:

  1. Rozbijamy kod ASCII tej literki na liczbę binarną:
65 1
32 0
16 0
8 0
4 0
2 0
1 1
0
  1. Teraz pakujemy ją do jakiegoś bufora - niech będzie to tablica bajtów. Nazwijmy ją "bufor".

bufor : <B>array of</B> byte = [01000001];

  1. Teraz sprawdzamy, czy rysunek pomieści naszą informację. Generalnie będziemy potrzebować w przybliżeniu (ilość liter+1)*8 div 3 piksli - należy więc sprawdzić, czy rysunek tyle pomieści. To robimy już w programie, nie sprawia to żadnych trudności.

  2. Po sprawdzeniu wchodzimy w pętelkę. Zapiszę ją schematycznie:

a) [Pobierz kolejny piksel] -> p : longint; // Pobiera kolejny piksel do "obróbki"

b) RozbijKolor (p, r,g,b); // "rozbija" kolor na składowe R, G i B

c)
r:=(r div 2)*2;
g:=(g div 2)*2;
b:=(b div 2)*2; // Teraz każdej składowej czyścimy ostatni bit liczby;

d)
r:=r+bufor[1];
g:=g+bufor[2];
b:=b+bufor[3]; // Ukrywamy informację w najmłodszych bitach składowych koloru

e) KodujKolor(r,g,b, p);

f) Delete(bufor,1,3); // Usuwamy 3 pierwsze wpisy bufora

g) p-> [Zapisz piksel na rysunku w odpowiednim miejscu];

... i to wszystko - wystarczy zapisać tak zmodyfikowaną bitmapę na dysku i nasz zakamuflowany rysunek jest gotowy. Pozostał aspekt odczytu - można to zrobić w łatwy sposób - posłużyć się operatorem mod:

bufor[1]:=r mod 2;
bufor[2]:=g mod 2;
bufor[3]:=b mod 2;

Pozostało jeszcze kilka uwag...

Ktoś może się w tym miejscu oburzyć i stwierdzić: Przecież, mimo, iż oko jest niedoskonałe, wystarczy obrazek powiększyć i stwierdzić, czy piksle mają zmienione kolory (ktoś podejrzliwy, przechwyciwszy taki obrazek mógłby go powiększyć. I tu się częściowo zgodzę - np. proszę spróbować ukryć coś w windowsowskim 'czarnym koszyku'. Faktycznie pod sporym powiększeniem (i narzędziem w stylu Photoshopa, pokazującym RGB) można zobaczyć drobne zmiany kolorów. Ale jak tylko wezmę dowolne zdjęcie, to nawet oglądając je pod ogromnym powiększeniem, a nie mając oryginału, nie mogę stwierdzić zmian.

Załóżmy zatem, że mamy do czynienia ze sprytniejszym szpiegiem, któremu udało się odkryć, że po spisaniu najmniej znaczących bitów uzyskuje ciekawy tekst. Co zrobić, aby tegoż tekstu nie odnalazł? To proste - przed wysłaniem wiadomość zaszyfrować, najlepiej tak, żeby nie były to same litery, lecz też inne znaki ASCII. Teraz ktoś powie: "To po co ten zaszyfrowany tekst jeszcze ukrywać"? No właśnie - po to, aby całkowicie ukryć fakt jego wysłania. Szpieg, po odczytaniu najmniej znaczących bitów dostanie mnóstwo nic (a właściwie prawie nic ;) ) nie znaczących bajtów i nie ma najmniejszych podstaw uważać, że jest to jakaś zaszyfrowana informacja, bo z podobnym skutkiem można "zdeszyfrować" mnóstwo BMP-ów dostając takie właśnie ciągi. Jasne?

Kolejna ważna sprawa - aby podczas przesyłania ukryte dane nie uległy uszkodzeniu, nie wolno przekompresowywać rysunku na .GIF lub .JPG. Kompresja JPG - stratna, odbywa się właśnie kosztem najmniej znaczących bitów, czyli po prostu niszczy naszą informację. GIF, z kolei, stosuje paletę indeksowaną, czyli zmniejsza ilość kolorów, a my przecież potrzebujemy wszystkich, 256<SUP>3</SUP> kolorów...

No i ostatnia wazna rzecz - jeśli kamuflujemy tekst w jakimkolwiek BMPie, należy koniecznie pamiętać o zapisaniu go jako BMP <B>24 bitowy</B>, bo właśnie takiego kodowania kolorów stosujemy; inaczej rysunek zostanie źle otwarty (gdy zapomniałem zmienić format, paint wykonał nieprawidłową operację (!) ) lub, w zależności od języka programowania, może zostać zapisany z powrotem jako np. 256- lub 16bit-kolorowy, co znów zniszczy zakamuflowany tekst.

Udanej zabawy ze steganografią życzy
-- Spook.

5 komentarzy

Z tego co się w szkole nauczyłem, myślałem, że:
(10000001)2=1(27)+0+0+0+0+0+0+0+1(20)=(128+1)10=(129)10, a nie tak jak Pan napisał 65. Chyba, że się mylę...
Swoją drogą ciekawy artykulik :)
Pozdrawiam Serdecznie.

a mój BMP-Hide w dziale delphi? to już test właśnie z artykułu Adama Boducha pisanego w C, a przeze mnie napisanego w Delphi (jakieś 3 wersje), poza tym szybszybszy i sprawniejszy. może troche trudny w skapowaniu, ale na pewno działający. twój kod mówi tak dużo że na prazwdę więcej nie trzeba (rozbijkolor - co to ma być? uzywa się zazwyczaj np. GetRValue itp, a poza tym jest coś takiego jak scanlines.

dał byś tego jakieś źródło bo jakos nic mi to nie mówi :/

  1. Wybacz, nie przeglądałem jeszcze wszystkich artykułów na 4programmers...
  2. No, powiedzmy. Niemniej jednak piszę w Delphi i dlatego zdecydowałem się zamieścić go tu.
  1. Najpierw spójrz tu: http://4programmers.net/view.php?id=99
  2. To raczej dział Algorytmy przydałby się niż Delphi.