📜 ⬆️ ⬇️

Implementation of the Kutter-Jordan-Bossen method in MATLAB

Introduction

Good day, users of Habr!

This article focuses on the software implementation of the Kutter-Jordan-Bossen steganographic method in MATLAB.

For those who do not know, I will explain: steganography is the science of the hidden transfer of information by keeping in secret the very fact of transmission. In this method, the text information is recorded in the image according to a specific algorithm. You can familiarize yourself with the algorithm itself in a post dated March 11, 2011 “Kutter-Jordan-Bossen steganographic method”. A more detailed description of the algorithm can be found in the article Zaschelkin, Ivashchenko, Ivanov "Improving the steganographic concealment of Kutter-Jordan-Bossen data . "
')
Now let's go directly to the code.

Main script

In connection with a sufficiently large amount of code, it was inconvenient to write it in one function, therefore, the code is divided into several functions. The script, which code is shown below, allows you to encrypt a text message in an image, calling each function in turn.

close all %      MATLAB clear; %     clc; %     %   RGB- rgbImg = imread('BMW-e36.bmp'); %      figure(1) imshow(rgbImg); title(['  *.bmp   RGB']); %     [ txt, bin_txt ]=read_txt(); disp(txt); %         [ rgbImgtxt, s, coords ]=KDB_write( rgbImg, bin_txt ); figure(2) imshow( rgbImgtxt ); title([ '   ' ]); %       [txt_new]=KDB_pull_out(rgbImgtxt,s,coords); disp(txt_new) 


The first three lines are not required for the script to work correctly, however, they can make the process of working with the script more convenient by removing the data obtained from the last call of the script from the computer’s memory.

 close all %      MATLAB clear; %     clc; %     


The image is read into a variable using the imread command, and the name of the file is indicated in parentheses and, if necessary, the path to it.

 rgbImg = imread('BMW-e36.bmp'); 


The read image is displayed on the screen using the imshow command, but to improve the perception of the result, the commands figure (create a new graphic window) and title (add a name in the graphic window) are used.

 %      figure(1) imshow(rgbImg); title(['  *.bmp   RGB']); 




Read image.

The text message to be encrypted must be read from the file and converted to binary. This task is performed by the read_txt function. This function returns to the script that called it two variables txt, bin_txt, in which the read text is written in symbolic as well as in binary form. The disp (txt) command is used to display the text on the screen.

 %     [ txt, bin_txt ]=read_txt(); disp(txt); 


To write information to an image, the KDB_write function is called. For this function to work, you must pass into it the value of the following variables: rgbImg, bin_txt. The first variable contains the original image, and the second - the text converted to binary numbers. The function returns the values ​​of the following variables: rgbImgtxt, s, coords.

In the three-dimensional array rgbImgtxt recorded image with embedded data. The vector s stores the size of the two-dimensional array bin_txt. The three-dimensional coords array contains the coordinates of the pixels that have undergone a change, and is a kind of key for the subsequent extraction of embedded information. After performing the function, an image is displayed on the screen, with embedded data.

 %         [ rgbImgtxt, s, coords ]=KDB_write( rgbImg, bin_txt ); figure(2) imshow( rgbImgtxt ); title([ '   ' ]); 


The KDB_pull_out function extracts the embedded information from the image and returns the encrypted text to the user, which is displayed on the screen.

 [txt_new]=KDB_pull_out(rgbImgtxt,s,coords); disp(txt_new) 


And now we will consider the work of each function separately.

The function of reading information from a text file

After the original image has been read, it is necessary to read the text message. To do this, use the read_txt () function.

 function [ txt, bin_txt ] = read_txt ( ) %         %            %  txt,      txt_r=''; txt=''; text1=fopen('message.txt','r'); while feof(text1)==0 line=fgetl(text1); txt_r=char(txt_r,line); end fclose(text1); txt(1,:)=txt_r(find((double(txt_r)>1040) + (double(txt_r)<1103))); s=size(txt); txt=txt(2:2:s(2)); bin_txt=dec2bin(double(txt))-'0'; end 


The main task of this function is to read information from a text document and convert it to the optimal form for embedding.

The file is opened using the fopen function in read mode, as indicated by the 'r' parameter.
Using a while loop, the file will be read until its end is reached (then the feof function will produce a value of 1 and the loop will stop executing).

In the body of the loop, the fgetl function is executed, which returns a string from the file, removing the end of line character. This line is written to the variable line. After that, the line variable is appended to the end of the text variable txt_r.

After the information is received, the text file must be closed.

 txt_r=''; txt=''; text1=fopen('message.txt','r'); while feof(text1)==0 line=fgetl(text1); txt_r=char(txt_r,line); end fclose(text1); 


After that, using the find function, only the characters in the ASKII table are numbered from 1040 to 1103 are written into the txt variable in one line (the list includes Latin characters, Cyrillic, Arabic numerals, and punctuation marks). To do this, you need to convert a variable of type char to double.

To convert text characters to binary code, they are first converted to double type, and then the dec2bin function is used. In order to get the final result in the form of a numeric array, rather than a string, you need to subtract the '0' symbol from dec2bin (double (txt)). Matlab will perform these actions with character codes and return the result in numerical form.

 txt(1,:)=txt_r(find((double(txt_r)>1040) + (double(txt_r)<1103))); s=size(txt); txt=txt(2:2:s(2)); bin_txt=dec2bin(double(txt))-'0'; 


The function of recording information in the image

 function [ rgbImgtxt, s, coords ] = KDB_write( rgbImg, bin_txt ) %       -- %     1    1  , %         ,     L=0.1; % ,        r=5; %      s=size(bin_txt); simg=size(rgbImg); coordy=randi([4,simg(1)-3],s(1),s(2),r); coordx=randi([4,simg(2)-3],s(1),s(2),r); coords=cat(3, coordy, coordx); rgbImgtxt=rgbImg; for i=1 : s(1) for j=1 : s(2) for k=1 : r %   Y=(0.298*rgbImg(coords(i,j,k),coords(i,j,k+3),1))+(0.586*rgbImg(coords(i,j,k),coords(i,j,k+3),2))+(0.114*rgbImg(coords(i,j,k),coords(i,j,k+3),3)); if (Y==0) Y=5/L; end %        %    if (bin_txt(i,j)==1) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k), coords(i,j,k+3), 3)+L*Y); else rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k), coords(i,j,k+3), 3)-L*Y); end if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)>255) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=255; end if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)<0) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=0; end end end end end 


This function is intended for embedding information by the Kutter-Jordan-Bossen method into an image saved in bmp format. The original image and the binary code of the text to be embedded are transferred from the main script to it.

The function determines the size of the array with binary character codes and the size of the image. On the basis of the results obtained, two three-dimensional arrays are randomly generated, containing the coordinates of the pixels into which the embedding will be performed. The number of layers in each array depends on the value of the variable r, is responsible for how many times one bit will be embedded. In the first array coordinates are recorded vertically, and in the second - horizontally. For convenience, these arrays are combined into one three-dimensional array.

 s=size(bin_txt); simg=size(rgbImg); coordy=randi([4,simg(1)-3],s(1),s(2),r); coordx=randi([4,simg(2)-3],s(1),s(2),r); coords=cat(3, coordy, coordx); 


The function uses three nested loops. Two of them allow you to access the corresponding bits of the text message, the third - rewrites the bits.

 for i=1 : s(1) for j=1 : s(2) for k=1 : r 


The variable Y is recorded in the brightness of the pixel, in which the embedding will be carried out. To determine it, it is necessary to determine the brightness value of each channel. Image format bmp is stored as a three-dimensional matrix consisting of three layers. In each layer recorded the brightness value for a particular color (from 0 to 255). The resulting brightness value is multiplied by a certain constant and summed with the brightness values ​​of other channels. If the brightness value is 0, you need to assign a variable a value of 5 / L.

 Y=(0.298*rgbImg(coords(i,j,k),coords(i,j,k+3),1))+(0.586*rgbImg(coords(i,j,k),coords(i,j,k+3),2))+(0.114*rgbImg(coords(i,j,k),coords(i,j,k+3),3)); if (Y==0) Y=5/L; end 


The direct embedding of information occurs using a conditional operator. If the text message bit is 1, the brightness of the blue channel of the image is increased by an amount equal to the product of L * Y. Otherwise, this value will be subtracted from the brightness of the blue channel of the image. In order to avoid possible overflow, you need to change the data type of uint8 to double.

 if (bin_txt(i,j)==1) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k),coords(i,j,k+3),3)+L*Y); else rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=double(rgbImg(coords(i,j,k),coords(i,j,k+3),3)-L*Y); end 


If, after changing the brightness of the blue channel, the brightness value exceeds 255, then you need to set it equal to 255. And if the brightness value is less than 0, then set it equal to 0.

  if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)>255) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=255; end if (rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)<0) rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)=0; end 


After all the loop iterations have been completed, the message will be fully embedded in the image. Visually determine what was embedded - it is almost impossible, especially with a small amount of embedded text.

The function returns three variables to the script that caused it: rgbImgtxt, s, coordinates.



Image with embedded information

To see the changes made to the image is almost impossible. You can verify this by comparing the enlarged fragments of the image before and after embedding information into it.



Fragment of the original image



Fragment of the image with embedded information

The function of extracting information from the image

 function [ txt_new ]=KDB_pull_out( rgbImgtxt, s, coords ) %       -- %         %   .      %          . rgbImgprog=rgbImgtxt; sigma=3; r=5; for i=1 : s(1) for j=1 : s(2) for k=1 : r %    rgbImgprog=(double(sum(rgbImgtxt(coords(i,j,k)-sigma:coords(i,j,k)+sigma,coords(i,j,k+3),3)))+double(sum(rgbImgtxt(coords(i,j,k),coords(i,j,k+3)-sigma:coords(i,j,k+3)+sigma,3)))-2*double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)))/(4*sigma); %        %    del=double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3))-rgbImgprog; if ( and(del==0,rgbImgprog==255) ) del=0.5; end if ( and(del==0,rgbImgprog==0) ) del=-0.5; end if ( del>0 ) kat(k)=1; else kat(k)=0; end end txt_new_bin(i,j)=round(sum(kat)/r); end end txt_new_doub=bin2dec(num2str(txt_new_bin)); s=size(txt_new_doub); txt_new=char(reshape(txt_new_doub,s(2),s(1))); end 


For the function to work, it is necessary to transfer an image with embedded information, the size of the array in which the binary codes of characters were written, and an array with the coordinates of the pixels into which the embedding was performed.

The variable sigma sets the number of pixels spaced from a pixel with a modified brightness, which will be predicted for the initial brightness.

As in the previous function, three cycles are used here. They are responsible for accessing pixels with embedded information.

  sigma=3; r=5; for i=1 : s(1) for j=1 : s(2) for k=1 : r 


At each iteration of the cycle, the predicted brightness of the blue channel of the pixel is found and compared with the actual one.

In order to find the predicted brightness, the sum of the cells in the row is to the left and to the right of the pixel with the information, the cells located above and below the pixel. Since the brightness value of the blue channel of the pixel itself with the embedded information is added twice, it needs to be multiplied by 2 and subtracted, and then divide the resulting number by the number of pixels used in the brightness calculation, in this function it is 12.

 rgbImgprog=(double(sum(rgbImgtxt(coords(i,j,k)-sigma:coords(i,j,k)+sigma,coords(i,j,k+3),3)))+double(sum(rgbImgtxt(coords(i,j,k),coords(i,j,k+3)-sigma:coords(i,j,k+3)+sigma,3)))-2*double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3)))/(4*sigma); 


In order to decrypt the message, it is necessary to find the difference between the actual brightness of the blue channel and the predicted one. Two options should also be considered when the difference between actual and predicted brightness is 0.

In the first case, if the difference is 0, and the predicted brightness is 255, then any positive number, for example, 0.5, should be written to the variable del.

If the difference and the predicted brightness are 0, then any negative number is written to the variable del.

 del=double(rgbImgtxt(coords(i,j,k),coords(i,j,k+3),3))-rgbImgprog; if ( and(del==0,rgbImgprog==255) ) del=0.5; end if ( and(del==0,rgbImgprog==0) ) del=-0.5; end 


In order to extract a message, it is necessary to compare the value of the variable del from 0. In case it is greater than 0, then 1 is written in the vector kat, otherwise - 0.

 if ( del>0 ) kat(k)=1; else kat(k)=0; end 


Since each bit is written to the image a certain number of times, (in our case, 3 times), we get a vector of three digits. These figures may be different, since the extraction of information is probabilistic in nature. In order to find the true value of the ciphertext bit, it is necessary to find the arithmetic average of all elements of the vector and perform rounding.

 txt_new_bin(i,j)=round(sum(kat)/r); 


After the value of all bits of the message is found, they must be converted to type char and written as a string:

 txt_new_doub=bin2dec(num2str(txt_new_bin)); s=size(txt_new_doub); txt_new=char(reshape(txt_new_doub,s(2),s(1))); 


After this, the function will transfer the txt_new variables to the script with the extracted message. This message may differ slightly from the source text, but remains readable. The occurrence of errors is due to the fact that the brightness prediction does not always give an objective result (for example, the presence of a large number of small contrasting details on the image).

I embedded the phrase “Bavarian Motor Works” into the image. As a result of 10 attempts at embedding-extracting information, I obtained the following results:

     >     0                     


In order to avoid this inaccuracy, you can increase the number of embeddings of one bit and increase the size of the “cross”, according to which brightness is predicted. However, these methods are effective until a certain point. In order to achieve the best result, it is necessary to use robust coding.

Thank you for attention!

List of sources used:

1. "Kutter-Jordan-Bossen's steganographic method."
2. Article by Zaschelkin, Ivashchenko, Ivanov “Improvement of the steganographic concealment of Kutter-Jordan-Bossen data” .

Source: https://habr.com/ru/post/230747/


All Articles