Implementation of the Koch-Zhao steganographic method in Ruby
The Koch-Zhao algorithm for embedding information uses the frequency domain of the container and consists in the relative replacement of the values of the coefficients of the discrete cosine transform (DCT). The image is divided into blocks of 8 × 8 pixels and DCT is applied to each block. Each block is suitable for recording one bit of information.
So the hiding algorithm will be as follows:
- iterate the image with a double array in increments of 8
- at each iteration we create a temporary array of 8x8 pixels, each element of which will be a set of three pixel colors.
- apply DCT to this array, and get an array of coefficients of size 8x8
- select 2 coefficients and calculate their difference modulo
- if the difference is less than or equal to 25, then assign the first coefficient a positive value of the second + constant, or the same but with a minus sign (this called bit transfer)
- if the difference is less than or equal to -25, then the same actions are only for the second coefficient.
- then the reverse DCT is applied to transfer our coefficients back to the spatial domain.
- then copy the new color values to the image.
The implementation of the DCT and ODKP functions in the Ruby language is as follows:
def dct(dct,arr)
s=0
(0..7).each do |i|
(0..7).each do |j|
temp = 0.0
(0..7).each do |x|
(0..7).each do |y|
temp = temp + $cos_t[i][x]*$cos_t[j][y]*arr[x][y][:blue]
end
end
dct[i][j] = $e[i][j]*temp
end
end
return dct
end
def idct(dct,arr)
(0..7).each do |i|
(0..7).each do |j|
temp = 0
(0..7).each do |x|
(0..7).each do |y|
temp += dct[x][y]*$cos_t[x][i]*$cos_t[y][j]*$e[x][y]
arr[i][j][:blue] = (temp > 255) ? 255 : (temp < 0 ) ? 0 : temp.round;
end
end
end
end
return arr
end
$ cos_t and $ e are global array variables with values already populated.
It should be noted that the method is quite resistant to image distortion, even to its significant change, but is not applicable to hide large amounts of data.
Full code of the script that implements hiding / reading information:
require 'RMagick'
include Magick
$cos_t = [ [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
[0.9807853, 0.8314696, 0.5555702, 0.1950903,
-0.1950903,-0.5555702,-0.8314696,-0.9807853],
[0.9238795, 0.3826834,-0.3826834,-0.9238795,
-0.9238795,-0.3826834, 0.3826834, 0.9238795],
[0.8314696,-0.1950903,-0.9807853,-0.5555702,
0.5555702, 0.9807853, 0.1950903,-0.8314696],
[0.7071068,-0.7071068,-0.7071068, 0.7071068,
0.7071068,-0.7071068,-0.7071068, 0.7071068],
[0.5555702,-0.9807853, 0.1950903, 0.8314696,
-0.8314696,-0.1950903, 0.9807853,-0.5555702],
[0.3826834,-0.9238795, 0.9238795,-0.3826834,
-0.3826834, 0.9238795,-0.9238795, 0.3826834],
[0.1950903,-0.5555702, 0.8314696,-0.9807853,
0.9807853,-0.8314696, 0.5555702,-0.1950903] ]
$e = [ [0.125, 0.176777777, 0.176777777, 0.176777777,
0.176777777, 0.176777777, 0.176777777, 0.176777777],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25],
[0.176777777, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25]];
def insert_message(image,text)
i = image.copy
dct = Array.new(8).map!{Array.new(8)}
k = 0
l = 0
s=0
temp = Array.new(8).map!{Array.new(8)}
(0..(image.columns - 1)).step(8) do |i|
(0..(image.rows - 1)).step(8) do |j|
break if l >= text.length*8
(0..7).each do |x|
(0..7).each do |y|
temp[x][y] = {
:red=>image.pixel_color(j+x,i+y).red,
:green=>image.pixel_color(j+x,i+y).green,
:blue=>image.pixel_color(j+x,i+y).blue
}
end
end
dct = dct(dct,temp)
k = dct[3][4].abs - dct[4][3].abs
if get_bit(text,l)
if k<=25
dct[3][4] = (dct[3][4]>=0) ? dct[4][3].abs + 150 : -1*(dct[4][3].abs + 150)
end
else
if k>=-25
dct[4][3] = (dct[4][3]>=0) ? dct[3][4].abs + 150 : -1*(dct[3][4].abs + 150)
end
end
xt = temp.clone
temp = idct(dct,temp)
(0..7).each do |x|
(0..7).each do |y|
pixel = Pixel.new(temp[x][y][:red],temp[x][y][:green],temp[x][y][:blue])
image.pixel_color(j+x,i+y,pixel)
end
end
l=l+1
end
break if l >= text.length
end
image.write('cr.bmp')
return image
end
def read_message(image)
k = 0
s=0
out = []
l=0
a=''
p=0
b=0
dct = Array.new(8).map!{Array.new(8)}
temp = Array.new(8).map!{Array.new(8)}
(0..(image.columns - 1)).step(8) do |i|
(0..(image.rows - 1)).step(8) do |j|
(0..7).each do |x|
(0..7).each do |y|
temp[x][y] = {
:red=>image.pixel_color(j+x,i+y).red,
:green=>image.pixel_color(j+x,i+y).green,
:blue=>image.pixel_color(j+x,i+y).blue
}
end
end
l=l+1
dct = dct(dct,temp)
k = dct[3][4].abs-dct[4][3].abs
if k>=25
a=1
elsif k<=-25
a=0
else
a=-1
break
end
b |= a << p
if p==7
out.push(b.chr)
b=0
end
p=(p<7) ? p+1 : 0
end
if a==-1
break
end
end
return out.join
end
def dct(dct,arr)
s=0
(0..7).each do |i|
(0..7).each do |j|
temp = 0.0
(0..7).each do |x|
(0..7).each do |y|
temp = temp + $cos_t[i][x]*$cos_t[j][y]*arr[x][y][:blue]
end
end
dct[i][j] = $e[i][j]*temp
end
end
return dct
end
def idct(dct,arr)
(0..7).each do |i|
(0..7).each do |j|
temp = 0
(0..7).each do |x|
(0..7).each do |y|
temp += dct[x][y]*$cos_t[x][i]*$cos_t[y][j]*$e[x][y]
arr[i][j][:blue] = (temp > 255) ? 255 : (temp < 0 ) ? 0 : temp.round;
end
end
end
end
return arr
end
def get_bit(str,pos)
return true if str[pos/8].ord & (1 << pos % 8) > 0
return false if str[pos/8].ord & (1 << pos % 8) <= 0
end
image = Magick::Image.read('src.bmp').first
insert_message(image,"qweqwe")
image = Magick::Image.read('dst.bmp').first
puts read_message image