Last Updated:

STEGANOGRAPHY: Secret Data In A Picture

Grup KPPDI
Grup KPPDI

Steg_main14208
Steganography adalah suatu teknik memasukkan data ke data lain. Yang akan saya bahas berikut adalah teknik Steganography dengan menyembunyikan data string ke dalam gambar Bitmap (BMP) dengan memasukkan tiap bit dari tiap karakter data ke data warna tiap pixel Bitmap. Meskipun demikian, Steganography dapat pula diaplikasikan pada media tipe data lainnya, seperti WAV, MP3, bahkan Corinna John telah mempublikasikan teknik Steganography dengan media tape (pita) kaset.

Kembali ke data string dan Bitmap, kita anggap bila kita mempunyai data string "Rahasia" dan sebuah Bitmap berukuran 100px (w) x 100px (h). Kita mulai dari karakter pertama dari string "Rahasia"
"R" -> 01010010
di mana 01010010 adalah kode biner untuk 82 - yang merupakan kode ASCII karakter "R".
Selanjutnya, tiap bit ini akan kita tambahkan di tiap pixel warna Bitmap yang tersedia, sehingga untuk kedelapan bit biner ini kita memerlukan delapan pixel warna Bitmap. Misalkan urutan delapan pixel warna pertama adalah:
FF8045FE
FF4532ED
FF239933
FF3788A1
FF542322
FF30FF32
FF903008
FFFF8998

Dengan mengingat bahwa tiap pixel warna terdiri dari 4 byte, di mana byte pertama adalah kode palette, 3 byte berikutnya masing-masing adalah warna-warna Red, Green dan Blue (RGB). Tiap bit dari 01010010 , dimulai dari LSB - atau bit paling kanan, akan kita masukkan ke tiap pixel warna di atas - mulai pixel paling awal - pada bit terakhirnya, sebagai berikut:

Bit "0" di-AND dengan bit terakhir pixel warna
Bit "1" di-OR dengan bit terakhir pixel warna
FF8045[FE] and 0 = FF8045FE
FF4532[ED] or 1 = FF4532ED
FF2399[33] and 0 = FF239932
FF3788[A1] and 0 = FF3788A0
FF5423[22] or 1 = FF542323
FF30FF[32] and 0 = FF30FF32
FF9030[08] or 1 = FF903009
FFFF89[98] and 0 = FFFF8998

Ingat, hanya bit terakhir dari byte terakhir yang pixel warna yang dipakai dalam operasi ini. Setelah selesai operasi pada kedelapan bit biner dari kode ASCII 82 ("R"), tiap pixel warna ini dikembalikan ke bitmap asalnya. Selanjutnya, proses operasi di atas dapat diterapkan pada karakter selanjutnya - dari data kita, yaitu "a" - terhadap 8 pixel warna berikutnya dari Bitmap yang tersedia. Untuk lebih jelas, berikut implementasinya dalam Delphi:

[sourcecode language='delphi']
{
input:
Data: Data Rahasia
Bitmap:bitmap sumber yang akan dimanipulasi
SaveTo: bitmap baru yang akan menampung hasil manipulasi

Saya sengaja memisahkan bitmap hasil manipulasi agar mudah dalam operasi selanjutnya
}
procedure EmbedToBmp(const Data:String; Bitmap, SaveTo:TBitmap);
var
bs:AnsiString;
pix:TColor;
i,j:integer;
count:Word;
bmp:TBitmap;
begin
//Tiap karakter Data diubah ke digit-digit biner yang mewakili kode ASCII-nya.
bs:=BytesToBits(data);
SaveTo.Assign(bitmap);

SaveTo.Canvas.Lock;
count:=1;
for i:= 0 to SaveTo.Height-1 do
begin
if count>MAX_BITS_COUNT then
break;
for j:= 0 to SaveTo.Width-1 do
begin
pix:=SaveTo.Canvas.Pixels[j,i];
//Ambil tiap pixel untuk tiap bit Data
if count>MAX_BITS_COUNT then
break;

if bs[count] = '1' then
pix:=pix or $00000001 //operasi OR untuk Bit "1"
else
pix:=pix and $FFFFFFFE; //operasi AND untuk Bit "0"
inc(count);
//kembalikan hasil operasi ke Bitmap
SaveTo.Canvas.Pixels[j,i]:=pix;
end;
end;
SaveTo.Canvas.Unlock;
end;

[/sourcecode]
Dalam kode di atas, kita perlu mengkonversi tiap byte/karakter Data Rahasia kita menjadi kode binernya. Kita buat lagi sebuah fungsi untuk menjalankan tugas ini:
[sourcecode language='delphi']
{
input:
Data: Data String yang tersusun dari karakter-karakter.
}
function BytesToBits(const data:String):AnsiString;
var
bit,b:Byte;
i,j:integer;
tmp:String;
count:Integer;
ans:AnsiString;

begin
if Data = '' then
begin
ShowMessage('Data masih kosong.');
exit;
end;
tmp:= MARK_STRING + Data;
//MARK_STRING diperlukan untuk validasi data nantinya
SetLength(ans, MAX_BITS_COUNT);
for i:= 1 to MAX_BITS_COUNT do
ans[i]:='0';
count:=0;
for i:=1 to length(tmp) do
begin
b:=ord(tmp[i]);
for j:=0 to 7 do
begin
bit:= (b shr j) and 1;
//Shift Right untuk menempatkan
//tiap bit pada posisi paling kanan (LSB)
ans[count+1]:=IntToStr(bit)[1];
inc(count);
end;
end;
Result:=Ans;
end;

[/sourcecode]
Sekarang semua sudah selesai. Hasil operasi ini pada Bitmap tidak akan tertangkap oleh mata, sehingga Bitmap hasil manipulasi akan tetap terlihat sama dengan Bitmap aslinya. Pertanyaan muncul, bagaimana membaca kembali data yang tersimpan dalam Bitmap tersebut? Berikut fungsi untuk membaca satu bit data dari tiap pixel warna Bitmap:
[sourcecode language='delphi']
{
input
Bitmap: bitmap yang akan dibaca datanya
Internal: menentukan apakah funsi ini akan dipakai untuk membaca data,
ataukah hanya mengetes apakah Bitmap mengandung data yang diperlukan
ingat kembali MARK_STRING pada fungsi BytesToBits() di atas
}
function ReadFromBMP(const Bitmap:TBitmap; const internal:Boolean=False):String;
var
bs:AnsiString;
pix:Byte;
i,j:integer;
count:Word;
mark:String;
begin
SetLength(bs, MAX_BITS_COUNT);
bitmap.Canvas.Lock;
count:=1;
for i:= 0 to bitmap.Height-1 do
begin
if count>MAX_BITS_COUNT then
break;
for j:= 0 to bitmap.Width-1 do
begin
if count>MAX_BITS_COUNT then
break;
pix:=Bitmap.Canvas.Pixels[j,i];
pix:=pix and $00000001;
bs[count]:=IntToStr(pix)[1];
inc(count);
end;
end;
bitmap.Canvas.Unlock;
//Ubah deretan bit yang didapat ke karakter-karakter yang sesuai
Result:=BitsToBytes(bs);
//pembacaan data atau cuma pengecekan?
if Internal then
exit; //internal use. jangan cek lagi valid atau tidak

//cek validitas data dengan memeriksa MARK_STRING
mark:=copy(Result, 1, length(MARK_STRING));
if mark = MARK_STRING then
Delete(Result, 1, length(MARK_STRING))
else
begin
//DATA tidak valid
//MessageBox(handle, 'Invalid BITMAP or BITMAP doesn''t contain secret data.','Warning',MB_ICONWARNING);
Result:='Warning.'+#13#10+'Invalid BITMAP or BITMAP doesn''t contain secret data.';
end
end;

[/sourcecode]
Pada fungsi di atas, kita memerlukan fungsi konversi deretan bit ke karakter-karakter yang sesuai:
[sourcecode language='delphi']
{
input:
Bits:Deretan bit yang akan dikonversi
}
function BitsToBytes(const bits: AnsiString): String;
var
tmp:String;
j:integer;
bit, b:byte;
str8:String;
begin
tmp:=Bits;
Result:='';
while Length(tmp)>0 do
begin
Ambil tiap depalan bit
str8:=copy(tmp,1,8);
Delete(tmp, 1,8);
//Posisi/urutan bit harus dibalik
str8:=ReverseBits(str8);
b:=0;
for j:=1 to 8 do
begin
if str8[j] = '1' then
bit:= 1
else
bit:= 0;
b:=(b shl 1) or bit;
end;
Result:=Result+chr(b);
end;
end;

[/sourcecode]
Dengan mengingat fungsi EmbedToBmp() mengambil bit-bit data biner dari kanan (LSB), sedangkan proses konversi BitsToBytes() memproses data dari kiri (MSB), maka posisi/urutan bit data biner harus dibalik. Kita gunakan fungsi berikut:
[sourcecode language='delphi']
{
input: deretan 8 bit
}
function ReverseBits(const bits: String): String;
var
i:integer;
begin
if length(bits)<>8 then
exit;
Result:=Bits;
for i := 1 to 8 do
Result[8-(i-1)]:=Bits[i];
end;

[/sourcecode]
KODE LENGKAP
Setelah memahami langkah-langkah di atas, sekarang kita lihat kode selengkapnya.
[sourcecode language='delphi']
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ExtDlgs;

Const
MARK_STRING_LENGTH = 5;
MARK_STRING = 'harum';
{
You may change either MARK_STRING or MARK_STRING_LENGTH within
corresponding changes
}

MAX_CHAR_COUNT = 100;
// maximum size (in chars/bytes) of secret data

MAX_BITS_COUNT = (MAX_CHAR_COUNT+MARK_STRING_LENGTH)*8;
{
100 = Max chars of secret text
5 = additional fo mark string
8 = bits count for each char
You may change length of data (MAX_CHAR_COUNT) to fit your needs,
means MAX_BITS_COUNT will automatically change,
again, means you must keep BITMAP dimension supplied by user
to be bigger than MAX_BITS_COUNT

at example above, MAX_BITS_COUNT will be (100+5) * 8,
which is 840, then BITMAP supplied for operation must
be at least 84pixels x 10pixels. But to avoid any unwanted illegal operation,
performing a 100 x 100 pixels BITMAP will be better idea.

Matter about MARK_STRING = 'harum'???
She's somebody special,
spirit of My Blog (http://jokorb.wordpress.com)!!!
}

{
This unit of code is copyrighted by jokorb@yahoo.co.uk
and is porperty of http://jokorb.wordpress.com

You may use it without any warranty, either all or part(s).
Please report any changes back to me (my email) or via
http://jokorb.wordpress.com/post/

}
type

Bits = array [1..MAX_BITS_COUNT] of byte;
TForm1 = class(TForm)
Image1: TImage;
Bevel1: TBevel;
Button1: TButton;
Button2: TButton;
Memo1: TMemo;
Button3: TButton;
Button4: TButton;
spd: TSavePictureDialog;
opd: TOpenPictureDialog;
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
function ReverseBits(const bits:String):String;
function BytesToBits(const data:String):AnsiString;
function BitsToBytes(const bits:AnsiString):String;
procedure EmbedToBmp(const Data:String; Bitmap, SaveTo:TBitmap);
function ReadFromBMP(const Bitmap:TBitmap; const internal:Boolean=False):String;
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

function TForm1.BytesToBits(const data:String):AnsiString;
var
bit,b:Byte;
i,j:integer;
tmp:String;
count:Integer;
ans:AnsiString;

begin
if Data = '' then
begin
ShowMessage('Data masih kosong.');
exit;
end;
tmp:= MARK_STRING + Data;
SetLength(ans, MAX_BITS_COUNT);
for i:= 1 to MAX_BITS_COUNT do
ans[i]:='0';
count:=0;
for i:=1 to length(tmp) do
begin
b:=ord(tmp[i]);
for j:=0 to 7 do
begin
bit:= (b shr j) and 1;
ans[count+1]:=IntToStr(bit)[1];
inc(count);
end;
end;
Result:=Ans;
end;

procedure TForm1.EmbedToBmp(const Data:String; Bitmap, SaveTo:TBitmap);
var
bs:AnsiString;
pix:TColor;
i,j:integer;
count:Word;
bmp:TBitmap;
begin
bs:=BytesToBits(data);
SaveTo.Assign(bitmap);

SaveTo.Canvas.Lock;
count:=1;
for i:= 0 to SaveTo.Height-1 do
begin
if count>MAX_BITS_COUNT then
break;
for j:= 0 to SaveTo.Width-1 do
begin
pix:=SaveTo.Canvas.Pixels[j,i];
if count>MAX_BITS_COUNT then
break;

if bs[count] = '1' then
pix:=pix or $00000001
else
pix:=pix and $FFFFFFFE;
inc(count);
SaveTo.Canvas.Pixels[j,i]:=pix;
end;
end;
SaveTo.Canvas.Unlock;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
fn:String;
bmp:TBitmap;
begin
//cek dulu dengan pemanggilan internal
if MARK_STRING = copy(ReadFromBMP(Image1.Picture.Bitmap,true),1,length(MARK_STRING)) then
begin
//sudah menyimpan data, tampil pesan.
MessageBox(handle, 'This BITMAP already contains secret data!.','Error',MB_ICONHAND);
exit;
end;
if spd.Execute then
fn:=spd.FileName
else
fn:='c:\SecretPicture.bmp';
bmp:=TBitmap.Create;
EmbedToBMP(memo1.text, image1.Picture.Bitmap,Bmp);
bmp.SaveToFile(fn);
bmp.Free;
end;

function TForm1.ReadFromBMP(const Bitmap:TBitmap; const internal:Boolean=False):String;
var
bs:AnsiString;
pix:Byte;
i,j:integer;
count:Word;
mark:String;
begin
SetLength(bs, MAX_BITS_COUNT);
bitmap.Canvas.Lock;
count:=1;
for i:= 0 to bitmap.Height-1 do
begin
if count>MAX_BITS_COUNT then
break;
for j:= 0 to bitmap.Width-1 do
begin
if count>MAX_BITS_COUNT then
break;
pix:=Bitmap.Canvas.Pixels[j,i];
pix:=pix and $00000001;
bs[count]:=IntToStr(pix)[1];
inc(count);
end;
end;
bitmap.Canvas.Unlock;
Result:=BitsToBytes(bs);
if InterNal then
exit; //internal use. jangan cek lagi valid atau tidak
mark:=copy(Result, 1, length(MARK_STRING));
if mark = MARK_STRING then
Delete(Result, 1, length(MARK_STRING))
else
begin
//MessageBox(handle, 'Invalid BITMAP or BITMAP doesn''t contain secret data.','Warning',MB_ICONWARNING);
Result:='Warning.'+#13#10+'Invalid BITMAP or BITMAP doesn''t contain secret data.';
end
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
Memo1.Text:=ReadFromBmp(Image1.Picture.Bitmap);
//ReadFromBmp(Image1.Picture.Bitmap);
end;

function TForm1.BitsToBytes(const bits: AnsiString): String;
var
tmp:String;
j:integer;
bit, b:byte;
str8:String;
begin
tmp:=Bits;
Result:='';
while Length(tmp)>0 do
begin
str8:=copy(tmp,1,8);
Delete(tmp, 1,8);
str8:=ReverseBits(str8);
b:=0;
for j:=1 to 8 do
begin
if str8[j] = '1' then
bit:= 1
else
bit:= 0;
b:=(b shl 1) or bit;
end;
Result:=Result+chr(b);
end;
end;

function TForm1.ReverseBits(const bits: String): String;
var
i:integer;
begin
if length(bits)<>8 then
exit;
Result:=Bits;
for i := 1 to 8 do
Result[8-(i-1)]:=Bits[i];
end;

procedure TForm1.Button1Click(Sender: TObject);
var
ok:Boolean;
begin
OK:=False;
if opd.Execute then
begin
With TBitmap.Create do
begin
try
LoadFromFile(opd.FileName);
if (width*height)<=MAX_BITS_COUNT then
begin
MessageBox(handle,'Gambar harus Bitmap berdimensi minimal 100*100 pixel.','Error',MB_ICONHAND);
exit;
end;
ok:=True;
except
Free;
end;
end;
end;
if OK then
Image1.Picture.LoadFromFile(opd.FileName);
end;

end.

[/sourcecode]
Mohon maaf, komentarnya dalam Bahasa Inggris, karena kode ini ditujukan untuk dipakai di FolderCustomizer.

DOWNLOAD
Silahkan download aplikasi terapan dari kode di atas:
Steganography Source + EXE (ganti ekstensi ke .ZIP setelah download).

REFERENSI
Berikut sebuah link yang mungkin bisa jadi referensi, hanya saja, ditulis dalam C/C++
http://www.codeproject.com/KB/security/HideIt.aspx

google_tags: delphi, steganography, jokorb, Secret Data, Bitmap, corinna john, reversebits, BytesToBits, BitsToBytes

Comments