Better, but still needs some dents worked out before I'd consider it stable.
Still might not work in Windows.
Changes:
*Image can be replaced.
*Allows both embedding and extracting (only one image open at a time, though).
*Encryption algorithm/mode can be specified.
*Shows info about the loaded image, including the amount of data that can be stored in it.
*Embed/extract as file have been removed, at least temporarily.
I took out the options to embed a file and to extract embedded data as a file, because their behavior is not as easy to handle in murgaLua as I'd hoped. The issue is mainly that steghide is a partially interactive program, and I haven't found a way to pass that interaction over to murgaLua. The extract-as-file is fine as long as you're ok with blindly forcing it every time, but I'm not ok with that. I'll probably be able to work something out with fl_file_chooser. Embed a file is more tricky. There is a variable limit to the amount of data that can be included in a JPEG, so the embed file has to be pretty small. If it's too big, steghide tells you it's too big, but that information does not get passed back to murgaLua for some reason. The save function has a similar issue, but since that is a vital part of the program I just have to accept it until i work out how to grab all necessary output from steghide.
The image info might need to be tweaked. Right now it uses "steghide info" to get the data capacity of the current image. It then shows that information as what I assume is the correct (approximate) number of available characters.
Is this right?:
If capacity is displayed in bytes, the number is rounded down to an integer.
If capacity is displayed in kb, the number is multiplied by 1024 and rounded down.
Should that be 1000 rather than 1024? Am I correct assuming 1byte=1character?
I may see if I can limit the size of the text buffer so the user doesn't have to guess.
#!/usr/bin/env murgaLua
-- limited GUI for steghide <steghide.sourceforge.net>
-- 2010 mikshaw
-- version 0.1
function apply_img(img)
image=img -- global image filename
local imgdata=fltk:Fl_JPEG_Image(img)
local w=imgdata:w()
local h=imgdata:h()
if w>0 and h>0 then imgbox:image(imgdata) end
local outname=string.gsub(image,"(.*)%..*","%1_stego.jpg")
outpoot:value(outname)
-- display image info/capacity in menu bar
local my_cmd=io.popen("steghide info "..image.." -p nothing")
local my_result=my_cmd:read("*a")
my_cmd:close()
local cap_str=string.gsub(my_result,".*capacity:%s*([%d%.]*%s*%a+).*","%1")
local cap_num=string.gsub(cap_str,"([%d%.]*).*","%1")
local cap_fmt=string.gsub(cap_str,"[%d%.%s]*(%a+)","%1")
if cap_fmt=="Byte" then cap_num=math.floor(cap_num); mult=1
elseif cap_fmt=="KB" then mult=1024 end
local chars=math.floor(cap_num*mult)
imgbar:menu(0):label(fltk.fl_filename_name(img)..
" | "..w.."x"..h.." | embed capacity: "..cap_str)
textfield:label("Your message ("..chars.." character limit for this image):")
textfield:redraw_label()
imgbox:size(w,h)
imgscr:position(0,0)
imgscr:redraw()
imgbar:redraw()
end
function choose_img()
local img=fltk.fl_file_chooser("Choose a JPEG","*.jp{e,}g",nil,0)
if img then apply_img(img) end
end
function choose_target()
local target=fltk.fl_file_chooser("Save As...","*.jp{e,}g",nil,0)
if target then outpoot:value(target) end
end
function write_file()
if not image then
tabs:value(t1)
fltk.fl_alert("You must choose an image file.")
return
end
if outpoot:value()=="" then
fltk.fl_alert("You must supply an output filename.")
return
end
if embpass:value()=="" then
fltk.fl_alert("You must set a password.")
return
end
local tmp=os.tmpname()..".txt"
local out=io.open(tmp,"w")
if out then
out:write(textfield:value())
out:close()
else
fltk.fl_alert("Can't read "..tmp.." for some reason")
end
if overw:value()==1 then ow=" -f " else ow="" end
os.execute("steghide embed -ef "..tmp.." -cf "..image.." -sf "..outpoot:value()..
" -p "..embpass:value()..ow.." -e "..enc_menu:text(enc_menu:value()).." "..mod_menu:text(mod_menu:value()))
os.remove(tmp)
end
function extract(cmd)
if not image then
tabs:value(t1)
fltk.fl_alert("You must choose an image file.")
return
end
if extpass:value()=="" then
fltk.fl_alert("You must supply a password.")
return
end
local my_cmd=io.popen("steghide "..cmd..image.." -p "..extpass:value())
local my_result=my_cmd:read("*a")
my_cmd:close()
extbuff:append(my_result.."\n")
end
bh=30; ww=500; wh=bh*20
w=fltk:Fl_Double_Window(ww,wh,"Steghide")
tabs=fltk:Fl_Tabs(0,0,ww,wh)
-- IMAGE TAB --
t1=fltk:Fl_Group(0,bh,ww,wh-bh,"image file")
t1:selection_color(46)
imgbar=fltk:Fl_Menu_Bar(0,bh,ww,bh)
imgbar:add("click here to choose an image")
imgbar:menu(0):callback(choose_img)
imgscr=fltk:Fl_Scroll(0,bh*2,ww,wh-bh*2)
imgbox=fltk:Fl_Box(0,bh*2,ww,wh-bh*2)
fltk:Fl_End() --imgscr
imgbox:align(85)
fltk:Fl_End() --t1
-- EMBED TAB --
t2=fltk:Fl_Group(0,bh,ww,wh-bh,"embed")
t2:selection_color(46)
embpass=fltk:Fl_Secret_Input(10,bh*2,ww-20,bh,"password:")
textfield=fltk:Fl_Multiline_Input(10,bh*4,ww-20,bh*10)
textfield:align(5)
textfield:label("Type your secret message here")
enc_menu=fltk:Fl_Choice(10,bh*15+10,ww/2-10,bh,"encryption:")
mod_menu=fltk:Fl_Choice(ww/2,bh*15+10,ww/2-10,bh,"mode:")
enc_menu:align(5); mod_menu:align(5)
enc_menu:add("none")
-- get encinfo
enc_info=io.popen("steghide encinfo")
enc_data=enc_info:read("*a")
enc_info:close()
-- first 2 lines are useless
enc_data=string.gsub(enc_data,".*%.%.%.%s*(.*)%s*\n","%1")
count=0
-- create table for encryption modes
enc_tab={}
for line in string.gmatch(enc_data,".-\n") do
count=count+1
e=string.gsub(line,"(.-):(.*)","%1")
m=string.gsub(line,".-:(.*)","%1")
enc_tab[count]={}
for i in string.gmatch(m,"%w+") do
table.insert(enc_tab[count],i)
end
if e == "rijndael-128" then enc_def=count end
enc_menu:add(e)
end
-- select the default encoding
enc_menu:value(enc_def)
outpoot=fltk:Fl_File_Input(10,bh*17,ww-20-bh,bh,"save as:")
ch_target=fltk:Fl_Button(ww-bh-10,bh*17,bh,bh,"@>")
ch_target:callback(choose_target)
outpoot:align(5); embpass:align(5)
save=fltk:Fl_Button(10,bh*18,bh*3,bh,"save")
save:callback(write_file)
overw=fltk:Fl_Round_Button(bh*4,bh*18,bh*4,bh,"overwrite an existing file?")
enc_menu:callback(function()
mod_menu:clear()
mod_menu:add(" ")
if enc_menu:value()==0 then mod_menu:value(0)
else
for k,v in pairs(enc_tab[enc_menu:value()]) do
mod_menu:add(v)
end
mod_menu:value(1)
end
end)
enc_menu:do_callback()
fltk:Fl_End() --t2
-- EXTRACT TAB --
t3=fltk:Fl_Group(0,bh,ww,wh-bh,"extract")
t3:selection_color(46)
exttext=fltk:Fl_Text_Display(10,bh*4,ww-20,bh*15)
extbuff=fltk:Fl_Text_Buffer()
exttext:buffer(extbuff)
extpass=fltk:Fl_Secret_Input(10,bh*2,ww-20,bh,"password:")
extpass:align(5)
infobutt=fltk:Fl_Button(10,bh*3,bh*3,bh,"get info")
infobutt:callback(function() extract("info ") end)
astext=fltk:Fl_Button(10+bh*3,bh*3,bh*3,bh,"extract text")
astext:callback(function() extract("extract -f -xf - -sf ") end)
-- The asfile feature doesn't work well with lua
--asfile=fltk:Fl_Button(10+bh*6,bh*3,bh*3,bh,"extract file")
--asfile:callback(function() extract("extract -sf ") end)
fltk:Fl_End() --t3
fltk:Fl_End() --tabs
if arg[1] then apply_img(arg[1]) end
w:resizable(tabs)
tabs:resizable(t1)
tabs:resizable(t2)
tabs:resizable(t3)
t1:resizable(imgscr)
t2:resizable(textfield)
t3:resizable(exttext)
w:show()
Fl:run()