Created
September 18, 2018 17:36
Star
You must be signed in to star a gist
ruby script to hide a string in an image
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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