Dawno temu napisałem mój . Post ten dotyczył bardzo interesującego problemu, a mianowicie steganografii. Oczywiście, rozwiązanie zaproponowane w tym starym wątku nie można nazwać steganografią w prawdziwym tego słowa znaczeniu. To tylko gra z formatami plików, jednakże, jest to dość interesująca gra.
Dziś spróbujemy zagłębić się nieco bardziej i przyjrzymy się algorytmowi LSB. Jeśli jesteście ciekawi, zapraszam pod kat. (Pod katem ruch: około megabajta.)
Przede wszystkim, należy zrobić krótkie wprowadzenie. Każdy wie, że celem kryptografii jest uniemożliwienie odczytu tajnych informacji. Oczywiście, kryptografia ma swoje obszary zastosowań, ale istnieje także inny sposób chronienia danych. Można nie szyfrować informacji, lecz udawać, że jej nie ma. To właśnie w tym celu wymyślono steganografię. Wikipedia zapewnia nas, że „steganografia (od greckiego στεγανοσ — ukryty i greckiego γραφω — piszę, dosłownie „tajne pismo”) — to nauka o ukrytej transmisji informacji poprzez zachowanie w tajemnicy samego faktu transmisji.
Oczywiście, nikt nie zabrania łączenia metod kryptograficznych i steganograficznych. Co więcej, w praktyce tak właśnie się robi, ale naszym zadaniem jest zrozumieć podstawy. Jeśli uważnie przeanalizować artykuł z Wikipedii, można dowiedzieć się, że w algorytmach steganografii występuje tzw. kontener i wiadomość. Kontener to dowolna informacja, która pomaga ukryć naszą tajną wiadomość.
W naszym przypadku kontenerem będzie obrazek w formacie BMP. Na początek przyjrzymy się strukturze tego pliku. Plik można zasadniczo podzielić na 4 części: nagłówek pliku, nagłówek obrazu, paletę i sam obraz. Na nasze potrzeby wystarczy wiedzieć, co jest zapisane w nagłówku.
Pierwsze dwa bajty nagłówka to sygnatura BM, następnie w podwójnym słowie zapisany jest rozmiar pliku w bajtach, kolejne 4 bajty są zarezerwowane i powinny zawierać zera, a w jeszcze jednym podwójnym słowie zapisane jest przesunięcie od początku pliku do właściwych bajtów obrazu. W 24-bitowym pliku bmp każdy piksel kodowany jest trzema bajtami BGR.
Teraz wiemy, jak dotrzeć do obrazu, czas zrozumieć, jak możemy zapisać w nim potrzebne informacje. Do tego przyda nam się metoda LSB. Istota metody polega na tym, że zamieniamy najmłodsze bity w bajtach odpowiedzialnych za kodowanie koloru. Załóżmy, że bajt naszej tajnej wiadomości to 11001011, a bajty w obrazie to…11101100 01001110 01111100 0101100111…, to kodowanie będzie wyglądać następująco. Podzielimy bajt tajnej wiadomości na 4 dwu-bitowe części: 11, 00, 10, 11 i zamienimy nimi najmłodsze bity obrazu: …11101111 01001100 01111110 0101100111…. Taka zamiana w ogólnym przypadku nie jest zauważalna dla ludzkiego oka. Co więcej, wiele starych urządzeń wyjściowych, nawet nie będzie w stanie wyświetlić takich nieznacznych zmian.
Oczywiście, można zmieniać nie tylko 2 najmłodsze bity, ale i dowolną ich liczbę. Istnieje tu następująca zależność: im większa liczba bitów, które zmieniamy, tym większą ilość informacji możemy ukryć, a tym większe zakłócenia w oryginalnym obrazie to wywoła. Dla przykładu, oto dwa obrazy:


Pomimo wszelkich starań nie byłem w stanie dostrzec różnicy między nimi, a jednak w drugim obrazie za pomocą opisanego metody ukryta jest poemat Lewisa Carrolla „Polowanie na Snarka”. Jeśli dotarłeś do tego momentu, z pewnością interesuje cię także jego realizacja. Jest dość prosta, ale od razu ostrzegam, że wszystko jest robione w Delphi. Powody są dwa: 1. Uważam, że Delphi to dobry język; 2. Ten program powstał w trakcie przygotowywania kursu z podstaw wizji komputerowej, a chłopaki, którym ten kurs prowadzę, na razie nie znają nic poza Delphi. Dla tych, którzy nie są zaznajomieni z składnią, warto wyjaśnić jedną rzecz shl x – przesunięcie bitowe w lewo o x, shr x – przesunięcie bitowe w prawo o x.
Zakładamy, że zapisujemy w kontenerze tekst, który jest przechowywany w zmiennej i zamieniamy najmłodsze dwa bity:
Kod do zapisu:
for i:=1 to length(str) do
begin
l1:=byte(str[i]) shr 6;
l2:=byte(str[i]) shl 2; l2:=l2 shr 6;
l3:=byte(str[i]) shl 4; l3:=l3 shr 6;
l4:=byte(str[i]) shl 6; l4:=l4 shr 6;
f.ReadBuffer(tmp,1);
f.Position:=f.Position-1;
tmp:=((tmp shr 2) shl 2)+l1;
f.WriteBuffer(tmp,1);
f.ReadBuffer(tmp,1);
f.Position:=f.Position-1;
tmp:=((tmp shr 2) shl 2)+l2;
f.WriteBuffer(tmp,1);
f.ReadBuffer(tmp,1);
f.Position:=f.Position-1;
tmp:=((tmp shr 2) shl 2)+l3;
f.WriteBuffer(tmp,1);
f.ReadBuffer(tmp,1);
f.Position:=f.Position-1;
tmp:=((tmp shr 2) shl 2)+l4;
f.WriteBuffer(tmp,1);
end;
Kod do odczytu:
for i:=1 to MsgSize do
begin
f.ReadBuffer(tmp,1);
l1:=tmp shl 6;
f.ReadBuffer(tmp,1);
l2:=tmp shl 6; l2:=l2 shr 2;
f.ReadBuffer(tmp,1);
l3:=tmp shl 6; l3:=l3 shr 4;
f.ReadBuffer(tmp,1);
l4:=tmp shl 6; l4:=l4 shr 6;
str:=str+char(l1+l2+l3+l4);
end;
A dla tych, którzy są kompletnie leniwi – .
Dziękujemy.
Źródło: habr.com
