Algorytm najmniej znaczącego bitu (ang. Least Significant Bit) jest najprostszą i najczęściej stosowaną metodą steganograficzną dla obrazów. Przyjmijmy, że chcemy ukryć dowolne dane binarne (tekst, obraz lub inne dane przedstawione jako ciąg zer i jedynek), takie które zmieszczą się w naszym nośniku. Jako nośnik przyjmijmy obraz w formacie PNG w 24-bitowym trybie RGB. Format PNG stosuje bezstratny system kompresji, dzięki temu daje nam bezpośredni dostęp do informacji o poszczególnych pikselach i możemy stworzyć zmodyfikowany obraz bez obaw o “zgubienie” części danych podczas zapisu. Tryb 24-bitowy oznacza, że do dyspozycji mamy 3 kanały, nie używamy kanału Alpha.
Każde dane cyfrowe można przedstawić w postaci binarnej. Wprowadzone informacje przed ukryciem są przetwarzane na tablicę bajtów, a każdy bajt jest zapisywany za pomocą 8 bitów.

Zmienna tekstowa String jako ciąg bitów.
Najmniej znaczącymi bitami są bity po prawej stronie w zapisie binarnym. Jeśli w każdej składowej zmienimy ostatni bit, to barwa piksela zmieni się na tyle nieznacznie, że oko ludzkie nie zarejestruje tej zmiany.
Kolor R: 11111110 G: 11111110 B: 11111110, czyli RGB (254, 254, 254), będzie tylko nieznacznie ciemniejszy niż rzeczywisty biały kolor.
Aplikacja przy odczytywaniu wiadomości powinna wiedzieć jak długo ma szukać ukrytych danych, dlatego na początku każdego pliku zostaje ukryta informacja o rozmiarze szyfrogramu. Informacje steganograficzne mają więc dwie części: wielkość komunikatu binarnego oraz samą wiadomość.
Poniżej został przestawiony przykład kodu w języku Java. Chociaż do operacji na bitach lepiej nadawałyby się języki niższego poziomu. Obraz jest wczytywany jako obiekt BufferedImage, co umożliwia pełną kontrolę nad pikselami obrazu. Metoda getRaster(), zwraca obiekt typu WritableRaster, w którym można modyfikować poszczególne piksele obrazu. Długość tablicy bajtów z danymi obrazu jest trzykrotnie większa niż obiektu BufferedImage. Klasa BufferedImage odwołuje się do pikseli obrazu, natomiast dalsza konwersja pliku rozkłada kolejne piksele na trzy kolory składowe RGB. Każdy zapisywany jest na 8 bitach, czyli 1 bajcie. W efekcie do zapisu danych o kolorze z jednego piksela potrzebujemy 3 bajtów. Tablica bajtów imageByte zawiera kolejno występujące po sobie składowe pikseli: RGBRGBRGB… Tablica msg zawiera treść ukrywanej wiadomości. W przypadku próby ukrycia tekstu, tablicę bajtów ze zwykłego typu String można uzyskać poprzez metodę getBytes() z opcjonalnym argumentem formatowania tekstu.
Przetworzenie nośnika i ukrywanej wiadomości na tablice bajtów.
public BufferedImage encryptImage(File file, byte [] text){ BufferedImage image = bIP.getBufferedImage(file); WritableRaster raster = image.getRaster(); DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer(); byte[] imageByte = buffer.getData(); byte msg[] = text; byte len[] = BinaryConversion.toByteArray(msg.length); LSB.encodeText(imageByte, len, 0); LSB.encodeText(imageByte, msg, 32); return image; }
Rozmiar jest przedstawiony jako typ Integer. Do zapisu takiej zmiennej w języku Java używane są 4 bajty, czyli 32 bity. Przyjęte zostało, że w każdym bajcie danych (jednej składowej koloru) zostanie zmieniony tylko jeden najmniej znaczący bit, aby zminimalizować zakłócenia w obrazie. Oznacza to, że ukrycie 1 bajta danych wymaga modyfikacji 8 bajtów obrazu. W efekcie do ukrycia liczby na 32 bitach, użyte zostaną 32 bajty, czyli pierwsze 32 elementy z tablicy reprezentującej obraz.
Pierwsze 32 bajty danych obrazu będą zawsze wykorzystane na osadzenie powyższej informacji. W kolejnych bajtach zawarta zostanie już konkretna wiadomość.

Zmiana obrazu w tablicę bitów
Zamiana liczby typu int na tablicę bajtów.
public class BinaryConversion { public static byte[] toByteArray(int length) { byte byte3 = (byte)((length & 0xFF000000) >>> 24); byte byte2 = (byte)((length & 0x00FF0000) >>> 16); byte byte1 = (byte)((length & 0x0000FF00) >>> 8 ); byte byte0 = (byte)((length & 0x000000FF) ); return(new byte[]{byte3,byte2,byte1,byte0}); } }
W powyższym kodzie liczba całkowita zostaje zamieniona na tablicę czterech bajtów, gdzie każdy bajt jest przedstawiony na 8 bitach. Wykorzystany został operator >>>, który w języku Java służy do przesuwania nieoznaczonych bitów w prawo.
Im więcej najmniej znaczących bitów w danym bajcie zostanie przeznaczonych do zmiany, tym dłuższą wiadomość można ukryć. Wprowadzenie znacznych modyfikacji do nośnika danych powoduje jednak pogorszenie jego jakości, a im bardziej obraz odbiega od oryginału, tym zwiększa się zagrożenie odkrycia wiadomości przez postronnego użytkownika.
Steganogram w najprostszym przypadku może być ukryty sekwencyjnie w kolejnych próbkach sygnału lub w wersji zmodyfikowanej tylko w określonych próbkach według wybranego klucza. Mogą to być przykładowo piksele o dwóch dominujących kolorach w obrazie lub rozmieszczenie w indeksach pikseli o określonej zależności, na przykład w co czwartym pikselu.
Jeżeli bitowo zapisana tajna informacja jest krótsza niż nośnik danych, to znaczy pozostają próbki niezmodyfikowane, dobrą praktyką jest uzupełnienie wiadomości, aby zwiększyć bezpieczeństwo danych. W przeciwnym razie ukryte dane mogłyby zostać łatwo wykryte za pomocą analizy statystycznej sygnału.
Przykładowo za pomocą trzech najmniej znaczących bitów każdej składowej 24-bitowego obrazu o rozdzielczości 800×600 punktów, można ukryć 1,44 mln bitów danych.

Ukrywanie bitów za pomocą podstawowego algorytmu LSB
W celu utrudnienia wykrycia tajnej wiadomości w aplikacji zmiana nie powinna nastąpić w kolejno występujących po sobie wartościach tablicy obrazu, ale co
określoną (najlepiej losową) liczbę elementów. Dla ulatwienia w przykładowym kodzie przeskok (ozanaczony jako jump)jest zależny od wielkości obrazu opakowującego oraz długości ukrywanej wiadomości. Dodatkowo przyjęte zostało, że zmianie ulega tylko jeden najmniej znaczący bit w danym bajcie.
Ukrywanie wiadomości.
public class LSB { public static byte[] encodeText(byte[] image, byte[] addition, int offset) { if(addition.length + offset > image.length){ throw new IllegalArgumentException("File_not_long_enough!"); } int jump =1; if(offset>0){ jump = (image.length - offset)/(addition.length*8); } for (int i=0; i < addition.length; i++) { int byteVal = addition[i]; for (int j=7; j >= 0; j--) { int bitVal = (byteVal >>> j) & 1; image[offset] = (byte)((image[offset]&0xFE) | bitVal); offset+=jump; } } return image; } }
Funkcja encodeText jako argumenty przyjmuje tablicę bajtów obrazu, tablicę bajtów osadzanych danych oraz przesunięcie. Wywoływana jest dwukrotnie. Pierwszy raz podczas ukrywania długości wiadomości. Wtedy przesunięcie zawsze wynosi 0, a liczba ukrywana jest w kolejnych 32 bajtach obrazu. Taki zabieg powoduje, że algorytm deszyfrujący wie w jaki sposób odczytać ukrytą wartość. Kolejne wywołanie ma na celu ukrycie treści właściwej tajnego przekazu. Przesunięcie wynosi 32 bajty, aby ominąć przestrzeń z ukrytą długością przekazu. Wiadomość właściwa nie musi już być ukrywana w kolejnych bajtach. Obliczany jest przeskok, nazwany w programie jako jump.
jump = (image.length − offset)/(addition.length ∗ 8);
Pozostała liczba bajtów obrazu, w których można ukryć dane, jest dzielona na ilość bitów tajnej informacji. Należy pamiętać, że przyjęta została zasada, że w jednym bajcie obrazu ukryty zostanie jeden bit wiadomości. Dzięki dodaniu przeskoku, wiadomość jest ukryta na obszarze całego pliku, a nie tylko jego początkowym fragmencie. Utrudni to wykrycie faktu zastosowania steganografii.
Zewnętrzna pętla for iteruje po każdym bajcie wiadomości przeznaczonej do ukrycia. Pętla wewnętrzna iteruje po kolejnych ukrywanych bitach wiadomości przy pomocy operatora >>>. W wyniku przesunięcia w prawo najbardziej znaczące bity ukrywanych danych stają się bitami najmniej znaczącymi i mogą być wstawione w miejsce najmłodszych bitów obrazu stanowiącego kontener. Zapis (byte)((image[offset] & 0xF E) | bitVal)
przedstawia dany bajt obrazu jako reprezentację bitową a operacja alternatywy z zatajanym bitem pozwala na zmianę najmłodszego bitu na ukrywaną wartość.

Po lewej zdjęcie wejściowe, po prawej zdjęcie z ukrytym obrazem
pizzy

Grafika osadzona w powyższym zdjęciu
W powyższym przykładzie jako nośnik danych został użyty obraz PNG o bardzo dużym rozmiarze 5152×3888 pikseli. W tak dużym kontenerze bez problemu można ukryć inny plik – użyte zostało zdjęcie pizzy o wymiarach 1155×684 pikseli. Róźnica między oryginalnym zdjęciem a przetworzonym nie jest widoczna gołym okiem.
Największą wadą metody LSB jest to, że zazwyczaj łatwo wykryć ją przez programy służące do stegoanalizy. Każde przekształcenie nośnika danych powoduje też utratę zapisanych informacji.
Poniżej znajdują się porównanie dwóch wersji ukrytej grafiki z pizzą programem do wykonywania steganoanalizy – Steganabara. Analizie zostały poddane dwa pliki z ukrytą wiadomością. W pierwszym obraz został osadzony tradycyjnym algorytmem LSB, który umieszcza dane w kolejnych bajtach. W drugim użyty został algorytm zmodyfikowany z programu St3g0, używający przeskoku i rozmieszczenia danych w całym nośniku.
Na plikach został zastosowany filtr bitowych masek dla najmniej znaczących bitów każdego kanału koloru. Filtr analizuje warstwy bitowe, oddziela je i łączy w całość. Przy zastosowaniu tej metody dla obrazu z ukrytą wiadomością dla kolejnych składowych bez skoku w algorytmie, wyraźnie widać oddzieloną poziomą linię końca ukrytego obrazu. W przypadku z rozłożeniem ukrywanych bitów na cały plik wynik jest zbliżony do analizy obrazu bez ukrytej wiadomości

analiza filtrem masek bitowych
Thanks for all your efforts that you have put in this. very interesting info .
I am always thought about this, appreciate it for posting.
Thanks for another informative website. The place else may I am getting that type of info written in such an ideal way? I have a project that I am simply now operating on, and I’ve been on the look out for such info.
Wow! This can be one particular of the most helpful blogs We’ve ever arrive across on this subject. Actually Fantastic. I’m also an expert in this topic therefore I can understand your effort.
Very nice article and straight to the point. I am not sure if this is in fact the best place to ask but do you people have any ideea where to employ some professional writers? Thx 🙂
Can I just say what a comfort to find somebody who truly understands what theyre talking about on the net. You certainly understand how to bring a problem to light and make it important. A lot more people really need to look at this and understand this side of the story. I cant believe youre not more popular because you definitely possess the gift.
I truly wanted to post a remark to be able to express gratitude to you for these fantastic concepts you are giving here. My time-consuming internet investigation has at the end been recognized with really good facts and strategies to go over with my two friends. I would declare that we site visitors are quite fortunate to live in a good network with so many wonderful professionals with very helpful concepts. I feel quite happy to have used your website and look forward to many more thrilling moments reading here. Thanks once more for a lot of things.