Главная » Delphi » Стеганография на коленке

0

Вообще говоря, к стеганографии (греч. steganos — секрет) относится любой прием, который позволяет скрыть сам факт наличия гайного сообщения: невидимые чернила, микрофотоснимки, условные знаки, тайники, кодовые сообщения в открытых радиопередачах и прочая шпионская мишура — все это есть предмет стеганографии. Легко догадаться, что она гораздо старше криптографии — если сообщение достаточно хорошо спрятано, то его можно и не шифровать. Мы рассмотрим прием стеганографии, который позволяет скрыть текст сообщения в цифровой картинке. Кстати, похожим, только более хич- рым способом в картинки вносят цифровую подпись — копирайт автора, который в принципе можно распознать, даже если сканировать репродукцию с такого цифрового оригинала, выполненную полиграфическим методом. Совершенно аналогичным способом можно хранить и скрытый текст (и вообще любую информацию) в аудиофайлах (для беззаголовочных файлов типа WAV это даже проще).

Для этого мы подробнее рассмотрим, как хранится изображение в файле типа BMP (см. также главу 10). Собственно картинкв там хранится в виде трех- байтных последовательностей, каждый байт в которой представляет собой интенсивность одной из трех составляющих модели RGB: красной, зеленой и синей. Если мы изменим всего один младший бит в каждом из этих байтов, визуально картинка не потеряет ровным счетом ничего— изменение каждой из составляющих на 1/256 долю глазом не заметно, даже если эта картинка — "Черный квадрат" Малевича. На практике можно менять и больше битов (до 4-х), но мы ограничимся одним4. Разумеется, файл не должен использовать сжатие (и это придется проверять), иначе мы все порушим. В нашем "прямолинейном" способе нам придется только позаботиться о том, чтобы изображение по дороге к адресату не подвергалось никаким преобразованиям типа сжатия или изменения размеров. Размеры изображения в нашем случае должны быть такими, чтобы произведение ширины и длины картинки в пикселах, умноженное на число бит на пиксел, превышало рвзмер текста сообщения, умноженный на 8. Для того чтобы понять, имеется ли а данном файле зашифрованное нами сообщение, будем добавлять к нему в начале свою сигнатуру— например, слово "steganographia". Придется также хранить в сообщении и его длину.

Для того чтобы составить корректный алгоритм, нам придется чуть-чуть углубиться в недра BMP-файла. Согласно описанию формата на сайте MSDN, заголовок собственно файла BMP (bitmapfi leheader) занимает всегда ровно 14 байт. Напомним (см. главу 10), что идентифицируется формат по первым двум байтам в этом заголовке (самым первым байтам файла), которые должны составлять комбинацию символов "ВМ". Следующее за этой сигнатурой поле bfsise— четырехбайтное число, которое должно быть равно размеру файла. Кстати, в процессе внесения изменений мы не будем менять размер файла, так что ничего а заголовке не нарушим и его без изменений можно перенести в новый файл. Вот если бы там была контрольная сумма — другое дело, но, к счастью, разработчики формата не стали морочить голоау такими сложностями. Все эти поля нам проверять не придется, потому что мы сразу попробуем загрузить исходную картинку на экран, и если ничего не выйдет, то увидим, что это не ВМР-файл.

Последние четыре байта заголовка (И — 14-й байты, т. е. с номерами 10, 11, 12 и 13, если присвоить первому байту файла номер 0) образуют поле bfoffBits — число типа DWord, которое содержит важную для нас информацию— начало в файле собственно байтового массива с изображением, в который мы и будем вносить изменения. Но прежде чем перейти к этому, нам надо выяснить еще две вещи: во-первых, сравнить размер данного байтового массива с размером текста сообщения, чтобы понять, уместится ли оно, по- вторых, проверить на всякий случай, не используется ли в файле сжатие, иначе, как уже говорилось, мы своим вмешательством можем все испортить. Неплохо также проверять, сколько цветов в изображении — если оно в оттенках серого, или просто имеет палитру не меньше 256 цветов (по одному байту, или 8 бит на пиксел), то все наши приемы точно так же будут работать, как и в случае TrueColor. Если же оно содержит меньше цветов, чем 256, то мы признаем этот файл непригодным.

После заголовка собственно файла qitmapfileheader идет структура bitmapinf’oheader, которая и содержит нужные нам сведения. Первые четыре байта этой структуры мы пропускаем, а в следующих восьми (начиная с банта номер 18, напомню, что мы договорились считать от начала файла с нуля) идет ширина (biwidth) и длина (biHeight) изображения в пикселах— по четыре байта (одному числу cword) на каждое измерение. Если пропустить еще два байта, то в следующем двухбайтном поле biBitCount будет число бит на пиксел (для TrueColor там будет стоять число 24=$ 18). А после него, начиная с байта номер 30, будет нужное нам двухбайтное поле ы compressed: если оно содержит нули, то файл не использует сжатия, в противном случае он для нашей цели непригоден.

После структуры bitmapinfoiieader и начинается массив пикселов, но фиксированный размер (40 байт) структура эта имеет только для модели TrueColor. в случае меньшего количества цветов в нес входит еще палитра, так что правильно определять начало массива с картинкой нужно по указанному выше полю заголовка bfOffBits. В случае True Color в этом поле должно содержаться число 54 ($36. т. е. 14+40) — проверьте!

Я специально приводил отсчет байтов от начала файла, потому что мы не будем заморачивать себе голову всякими структурами, а прочтем байты напрямую из файла по одному. Единственная сложность при этом, которую мы себе позволим, — преобразование формата чисел через указатели, ведь читаем мы побайтно, а с числами нам придется иметь дело, как типа Word (двухбай т- ными), так и типа nword (четырехбайтными). Еще об этом методе читайте также в главе 21, а здесь я его просто использую без особых пояснений — все и так будет понятно из текста программы. Так как размеры, сообщения и тем более картинки могут быть весьма значительны, придется ввести еще и нормальное (не побайтное) чтение файла— по причинам, изложенным в тон же главе 21, автор предпочитает использовать для этой цели нетипизированные указатели. Впрочем, основные операции чтения заголовка BMP и кодирования мы будем делать "по-старинке". До законченного образца мы программу здесь доводить не будем — увидите, что она получится и так весьма "навороченная", и сейчас нам важно показать суть дела на демонстрационном примере.

Итак, создадим новый проект (Glava 19V2), назовем его Steganos и поместим в папку с проектом картинку в ВМР-формате (okno.bmp— это вид из окна моего домика в деревне). В качестве шифруемого сообщения используем фрагмент работы пламенного большевика JI. Троцкого "Преданная революция"[2] (файл trotzki.txt).

Разместим на форме следующие компоненты: imagel на панели p^neli (у нее установим СВОЙСТВО BorderStyle В bsSingle И ОЧИСТИМ заголовок). Memol (у которого подключим вертикальную линейку прокрутки: Scrollers = ssVertical) и пять кнопок на двух панелях, которым придадим соответствующие заголовки. У Imagel установим В True свойства Proportional Н stretch. Кроме этого, поставим на форму диалог открытия файла opcnDiniogi. В конечном итоге все это должно выглядеть так, как показано на рис. 19.1.

Рис. 19.1. Форма проекта Steganos

У кнопок Button3 (Зашифровать текст) и Button^ (Расшифровать картинку) свойство Enabled установим в False— пока нет ни текста, ни картинок, расшифровывать и зашифровывать нечего. Текст программы я приведу без особых пояснений, основной алгоритм мы описали ранее, а остальное будет ясно из комментариев в тексте. Объявляем следующие переменные и типы:

type

ab=array [0..3] of byte;

wordp=-Aword;

longp=’>dword;

bytep=~byte;

abp=Aab;

var

Forml: TForml;

fnamep,fnamet,st,sttext:string;

fipic,fopic,ftext:file;

i,j,picsize,textsize,picoffs:integer;

xb,tb,ib:byte;

xw,yw:word;

plong:longp;

pword:wordp;

pab:abp;

pp:pointer;

pb:bytep;

При запуске программы инициализируем диалог и переменные:

procedure TForml.FormCreate(Sender: TObject); begin

OpenDialogl.InitialDir:=ExtractFileDir(Application.ExeName); picsize:=0; textsize:=0; end;

При нажатии на кнопку Открыть картинку:

procedure TForml.ButtonlClick(Sender: TObject); begin {открыть картинку) OpenDialogl.FileName: = "'; {очистим) if OpenDialogl.Execute then fnamep:=OpenDialogl.FileName else exit; try

Imagel.Picture.LoadFromFile(fnamep);

except

exit; (если не открывается – на выход! end;

assignfile(fipic,fnamep); (задали исходный файл с картинкой) reset(fipic,1); {проверка:I New(pab);

seek(fipic,16); (начиная с 18-го байта – размеры! for i:=0 to 3 do blockread(fipic,pabA[i],l) ; plong:=longp(integer(pab)); picsize^plong"; (ширина)

for i:=0 to 3 do blockreadffipicjpab"[i], 1); plong:=longp(integer(pab));

picsize:=picsize*plong"; (ширина*длину в пикселах} seek(fipic,28);

/начиная с 28-го байта – бит на пиксел,

потом компрессия по два байта) for i:=0 to 3 do blockread(fipic,pabA[il, 1); pword:=wordp(integer(pab)); xw:=pword"; (бит на пиксел) pword:=wordp(integer(pab)+2); yw: =pwo rd"; (компресса) if (xw<8) or (yw<>0) then begin

st:=’B файле ‘+ExtractFileN’ame( fnamep)+’ используется сжатко1 +? #10+’изображения или в нем слишком мало цветов.’+#10+ ‘Подберите другой BMP-файл.';

Application.MessageBox(Pchar(st),’Ошибкаmb_0K); picsize:=0; closefile(fipic); exit; end;

(проверяем размер, если текст уже открыт:)

if textsizeOO then

begin

if (picsize*xw)<(textsize*8) then begin

st:=’Файл ‘+ExtractFileName(fnamep)i ‘ имеет недостаточный размер’+ #10+’Подберите другой BMP-файл.'; Application.MessageBox(Pchar(st),’Ошибка’,mb_0K); picsize:=0;

Button3.Enabled:=False; /кнопка шифрации недоступна)

closefile(fipic); exi trend else Buttc,n3.Enabled:-True; (кнопка шифра или доступна) and;

Button<!.Enabled:=True;

(кнопка дешифрации доступна, когда открыта картинка/ seek(fipic,10); (начиная с 10-го байта – смещение/ for i:=0 to 3 do bicckread(fipic,pab"[i], 1); plong:=longp(integer(pab)); pico f f s:=plong";

(в picoffs – смещение массива пикселов от начала файла) Dispose(pab); closefile(fipic); end;

При нажатии на кнопку Открыть текст:

procedure TForml.Butuon2Click(Sender: TObject); begin (открыть текст/ OpenDialogl.FileName:=”; (очистим) if OpenDialogl.Execute then fnamet:=OpenDialogl.FileName else exit;

assignfile(ftext,fnamet); (задали исходный файл с текстом) reset(ftext,1); textsize:=filesize(ftext);

(проверяем размер, если картинка уже открыта:/

if picsize<>0 then

begin

if (picsize*xw)<(textsize*8) then begin

st: = ‘<J>aitn ‘+ExtractFileName (fnamet) + 1 слишком велик для выбранного изображения.’+ #10+’Подберите другой BMP-файл.'; Application.MessageBox(Pchar(st),’Ошибка’,mb_0K); textsize:=0;

Button3.Enabled:=False; (кнопка шифратор недоступна)

closefile(ftext);

exit;

end else Button3.Enabled:=True; (кнопка шифрации доступна/ end;

getmem(pp,textsLze);

blockread(ftext,ррл,textsize,j);(прочтем текст за один прием)

for i:=0 to texts .i.ze-1 do (и переведем в строку/

begin

pb:=bytер(integer(pp)+i);

sttext:=sttext+chr(рЬл); end;

freemem(pp,textsize); Memol.Text:=sttext; /вывелем в Memo) closefile(ftext); end;

Обратите внимание, что и при открытии картинки и при открытии текста используются процедуры проверки достаточности размера картинки — в зависимости от того, что было открыто раньше.

При нажатии на кнопку Зашифровать текст:

procedure TForml.Button3Click(Sender: TObject); begin (зашифровать текст) assignfile!fipic,fnamep); (исходный файл с картинкой) reset(fipic,1);

ChDir(ExtractFileDir(fnamep));

(на всякий случай устанавливаем папку\ assignfile(fopic,’0’+FxtraciFileName(fnamep));

(имя выходного файла) rewrite(fopic,1);

seek(fipic,picoffs); (все до pica СIs игнорируем) st:=IntToStr(length(sttext));

while length(st)<10 do st:=’0’+st; (числовое поле 10 знаков) sttext:=’steganographia’+st+sttext; (добавляем сигнатуру

и размер записи, всего 24 байта заголовок) for i:=l to length(sttext) do (основная процедура I begin

tb:=ord(sttext[i]); (очередной бант текста)

for j:=0 to 7 do

begin

blockread(fipic,ib,1); !очередной байт изображения! ib:=ib end $FE; (обнуляем младший бит изображения) xb:=tb shr j; /сдвигаем до нужного бита I xb:=xb and $01; (обнуляем все, кроме младшего бита) ib:=ib or xb; (записываем младший бит) blockwrite(fopic,ib,1); end; end;

(записываем остаток сразу куском:) j:=filesize (fipic)-filepos(fipic); getmem(pp, j+1) ; blockread(fipic,ррЛ, j, i) ; blockwrite(fopic,ррЛ,i,i);

freemem(pp, j+1); closefile(fipic); closefile(fopic);

st:=’TeKCT зашифрован в файле ‘+10’+ExtractFileName(fnamep); Application.MessageBox(Pchar(st),’Все отлично’,mb_OK); end;

Расшифровать картинку несколько проще, только проверок больше:

procedure TForml.Button4Click(Sender: TObject); begin {расшифровать картинку} Memol.Lines.Clear; {очищаем Memo) textsize:=0; {как будто текстового файла не было} Button3.Enabled:=False; {кнопка шифрации недоступна{ assignfile(fipic,fnamep); {исходный файл с картинкой} reset(fipic,1);

seek(fipic,picoffs); {все до picoffs игнорируем} st:=”;

for i:=l to 24 do {чтение заголовка) begin tb:=0;

for j:=0 to 7 do begin

blockread(fipic,ib,1); {очередной байт изображения/ ib:=ib and $01; {обнуляем все, кроме младшего бита} ib:=ib shl j; {сдвигаем до нужного битаI tb:=tb or ib; {записываем младший бит) end;

st:=st+chr(tb); {очередной байт заголовка) end;

if pos(‘steganographia1,st)=0 then {если там нет информации) begin

st:=’B файле ‘+ExtractFileName(fnamep)+’ отсутствует текст.'; Application.MessageBox(Pchar(st),’Ошибка’,mb_OK); exit; end;

delete (st, 1,14) ;

j:=StrTolntDef(st,0); {извлекаем длину}

if j=0 then

begin

st:=’B файле ‘+ExtractFileName(fnamep)+ ‘ длина сообщения равна 0.';

Application.MessageBox(Pchar(st),’Ошибка’,mb_0K); exit; end;

sttext:=”;

for i:=1 to j do (чтение сообщения) begin tb:=0;

for j :=0 to 7 do begin

blockread(fipic,ib,1); {очередной байт изображения) ib:=ib and $01;lобнуляем see, кроме младшего бита) ib:=ib shl j; (сдвигаем до нужного бита/ tb:=tb or ib; (записываем младший бит) end;

sttext:=sttext+chr(tb); (очередной байт сообщения) end;

Memol .Text: =sttext; (выведет»i б Memo) closefile(fipic); end;

Очистить текст может потребоваться, если мы загружаем новую картинку просто для расшифровки (тогда проверка размера не будет выполняться):

procedure TForml.Button5Click(Sender: TObject); begin (очистить текст) Memol.Lines.Clea r; textsize:=0;

Button3.Enabled:=Faise; Iкнопка шифрации недоступна) end;

Если вы рассмотрите новую картинку (в файле Ookno.bmp), то вы не найдете в ней никаких отличий от старой даже под большим увеличением. Тем не менее расшифровка будет исправно выдавать текст Льва Давидовича, как будто он там всегда был.

Никто, естественно, не мешает использовать стеганографию в совокупности с шифрованием, как мы это делали ранее, а также хранить любую другую информацию, кроме текста — скажем картинку в картинке (представляете себе газетную сенсацию: найдены чертежи секретного объекта в фотографии Анны Курниковой!). Для того чтобы довести программу до законченного варианта, которым можно пользоваться повседневно, нужно еще доделать много чего: как минимум, выводить куда-то имена открытых файлов (желательно и их размеры), ввести в диалог открытия нужные фильтры, ввести процедуру сохранения расшифрованного текста в файле, сделать меню, справку и т. п. Возможно, при больших объемах сообщений придется ввести ползунок и/или доработать программу в целях ускорения шифрования. Само по себе такое приложение можно делать весьма разнообразными способами, в зависимости от цели, так что я оставляю его доработку на откуп читателям.

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии