Skip to content

Instantly share code, notes, and snippets.

@esoterik
Created September 18, 2018 17:36
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save esoterik/317e2fa12fff5a5f73f0839a71dfaab4 to your computer and use it in GitHub Desktop.
ruby script to hide a string in an image
require 'rmagick'
require 'chunky_png'
# Simple least significant bit steganography. Hides a message in the
# least significant bit of the red channel of the image.
#
# Requires ImageMagick, Ruby 2.5, rmagick gem, chunky_png gem
#
# To encode a message:
# ruby lsb.rb encode image_file message encdoded_filename
#
# To decode a message:
# ruby isb.rb decode filename
class LSB
include ChunkyPNG
def initialize(filename:)
@filename = convert_to_png(filename)
@img = Image.from_file(@filename)
end
def encode(message:, stego_filename:)
message = message.force_encoding('UTF-8')
unless message.ascii_only?
puts 'Please only include ascii characters in the message.'
return 1
end
binary_message = message.unpack('B*')[0].split(//).map(&:to_i)
if img.area < binary_message.size
puts "Message requires #{binary_message.size} pixels to encode and the "\
"image contains only #{img.area} pixels."
return 1
end
binary_message += [0] * (img.area - binary_message.size)
img.height.times do |y|
img.width.times do |x|
img[x, y] = encode_pixel(img[x, y],
binary_message[y * img.width + x])
end
end
img.save(stego_filename)
return 0
end
def decode
message = []
img.height.times do |y|
img.width.times do |x|
message << (Color.r(img[x, y]) % 2)
end
end
message.each_slice(8).map { |s| s.map(&:to_s).join('') }
.map { |s| [s].pack('B*') }.join('')
end
private
attr_reader :img, :filename
def convert_to_png(file)
return file if file.split('.')[1] == 'png'
img = Magick::Image.read(file)[0]
img.format = 'png'
png_name = file.split('.')[0] + '.png'
img.write(png_name)
png_name
end
def encode_pixel(pixel, bit)
return pixel if (Color.r(pixel) + bit) % 2 == 0
Color.rgba(Color.r(pixel) + 1, Color.g(pixel),
Color.b(pixel), Color.a(pixel))
end
end
mode, filename = ARGV
lsb = LSB.new(filename: filename)
if mode == 'encode'
result = lsb.encode(message: ARGV[2].dup,
stego_filename: ARGV[3])
if result == 0
puts 'Success!'
else
puts 'Failed to encode.'
end
elsif mode == 'decode'
puts lsb.decode
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment