mirror of
https://github.com/cedricbonhomme/Stegano.git
synced 2025-06-28 03:06:14 +02:00
Re-ordered files.
This commit is contained in:
parent
93103a7a4c
commit
c656f3814d
14 changed files with 0 additions and 0 deletions
1
stegano/__init__.py
Executable file
1
stegano/__init__.py
Executable file
|
@ -0,0 +1 @@
|
|||
|
106
stegano/basic.py
Executable file
106
stegano/basic.py
Executable file
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
|
||||
#
|
||||
# For more information : http://bitbucket.org/cedricbonhomme/stegano/
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2010/10/01 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PIL import Image
|
||||
|
||||
def hide(img, message):
|
||||
"""
|
||||
Hide a message (string) in an image.
|
||||
|
||||
Use the red portion of a pixel (r, g, b) tuple to
|
||||
hide the message string characters as ASCII values.
|
||||
The red value of the first pixel is used for length of string.
|
||||
"""
|
||||
length = len(message)
|
||||
# Limit length of message to 255
|
||||
if length > 255:
|
||||
return False
|
||||
# Use a copy of image to hide the text in
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
index = 0
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
(r, g, b) = img.getpixel((col, row))
|
||||
# first value is length of message
|
||||
if row == 0 and col == 0 and index < length:
|
||||
asc = length
|
||||
elif index <= length:
|
||||
c = message[index -1]
|
||||
asc = ord(c)
|
||||
else:
|
||||
asc = r
|
||||
encoded.putpixel((col, row), (asc, g , b))
|
||||
index += 1
|
||||
return encoded
|
||||
|
||||
def reveal(img):
|
||||
"""
|
||||
Find a message in an image.
|
||||
|
||||
Check the red portion of an pixel (r, g, b) tuple for
|
||||
hidden message characters (ASCII values).
|
||||
The red value of the first pixel is used for length of string.
|
||||
"""
|
||||
width, height = img.size
|
||||
message = ""
|
||||
index = 0
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
r, g, b = img.getpixel((col, row))
|
||||
# First pixel r value is length of message
|
||||
if row == 0 and col == 0:
|
||||
length = r
|
||||
elif index <= length:
|
||||
message += chr(r)
|
||||
index += 1
|
||||
return message
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Point of entry in execution mode.
|
||||
from optparse import OptionParser
|
||||
usage = "usage: %prog hide|reveal [options]"
|
||||
parser = OptionParser(usage)
|
||||
parser.add_option("-i", "--input", dest="input_image_file",
|
||||
help="Image file.")
|
||||
parser.add_option("-o", "--output", dest="output_image_file",
|
||||
help="Image file.")
|
||||
parser.add_option("-s", "--secret", dest="secret",
|
||||
help="Your secret (Message, Image, Music or any binary file).")
|
||||
parser.set_defaults(input_image_file = './pictures/Lenna.png',
|
||||
output_image_file = './pictures/Lenna_enc.png',
|
||||
secret = 'Hello World!')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if sys.argv[1] == "hide":
|
||||
img = Image.open(options.input_image_file)
|
||||
img_encoded = hide(img, options.secret)
|
||||
img_encoded.save(options.output_image_file)
|
||||
|
||||
elif sys.argv[1] == "reveal":
|
||||
img = Image.open(options.input_image_file)
|
||||
print reveal(img)
|
1
stegano/exif/__init__.py
Normal file
1
stegano/exif/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
|
197
stegano/exif/minimal_exif_reader.py
Normal file
197
stegano/exif/minimal_exif_reader.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
"""
|
||||
This module offers one class, MinimalExifReader. Pass jpg filename
|
||||
to the constructor. Will read minimal exif info from the file. Three
|
||||
"public" functions available:
|
||||
imageDescription()--returns Exif ImageDescription tag (0x010e) contents,
|
||||
or '' if not found.
|
||||
copyright()--returns Exif copyright tag (0x8298) contents, or '' if not
|
||||
found.
|
||||
dateTimeOriginal()--returns Exif DateTimeOriginal tag (0x9003) contents,
|
||||
or '' if not found. If found, the trailing nul char
|
||||
is stripped. This function also takes an optional
|
||||
format string to apply time.strftime-style formatting
|
||||
to the date time.
|
||||
|
||||
Brought to you by Megabyte Rodeo Software.
|
||||
"""
|
||||
|
||||
# Written by Chris Stromberger, 10/2004. Public Domain.
|
||||
# Much is owed to Thierry Bousch's exifdump.py:
|
||||
# http://topo.math.u-psud.fr/~bousch/exifdump.py
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
class ExifFormatException(Exception):
|
||||
pass
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
class MinimalExifReader:
|
||||
IMAGE_DESCRIPTION_TAG = 0x010e
|
||||
COPYRIGHT_TAG = 0x8298
|
||||
EXIF_SUBIFD_TAG = 0x8769
|
||||
DATE_TIME_ORIGINAL_TAG = 0x9003
|
||||
|
||||
#---------------------------------------
|
||||
def __init__(self, filename):
|
||||
"""Pass in jpg exif file name to process. Will attempt to find tags
|
||||
of interest."""
|
||||
|
||||
self.tagsToFind = {self.IMAGE_DESCRIPTION_TAG:'',
|
||||
self.COPYRIGHT_TAG:'',
|
||||
self.DATE_TIME_ORIGINAL_TAG:''}
|
||||
|
||||
# Read first bit of file to see if exif file.
|
||||
f = open(filename, 'rb')
|
||||
firstTwoBytes = f.read(2)
|
||||
if firstTwoBytes != '\xff\xd8':
|
||||
f.close()
|
||||
raise ExifFormatException("Missing SOI marker")
|
||||
|
||||
appMarker = f.read(2)
|
||||
# See if there's an APP0 section, which sometimes appears.
|
||||
if appMarker == '\xff\xe0':
|
||||
#print "Skipping app0"
|
||||
# Yes, we have app0. Skip over it.
|
||||
app0DataLength = ord(f.read(1)) * 256 + ord(f.read(1))
|
||||
app0 = f.read(app0DataLength - 2)
|
||||
appMarker = f.read(2)
|
||||
|
||||
if appMarker != '\xff\xe1':
|
||||
raise ExifFormatException("Can't find APP1 marker")
|
||||
|
||||
exifHeader = f.read(8)
|
||||
#import binascii
|
||||
#print binascii.hexlify(exifHeader)
|
||||
if (exifHeader[2:6] != 'Exif' or
|
||||
exifHeader[6:8] != '\x00\x00'):
|
||||
f.close()
|
||||
raise ExifFormatException("Malformed APP1")
|
||||
|
||||
app1DataLength = ord(exifHeader[0]) * 256 + ord(exifHeader[1])
|
||||
#print app1DataLength
|
||||
|
||||
# Read exif info starting at the beginning of the self.tiff section.
|
||||
# This is 8 bytes into the app1 section, so subtract 8 from
|
||||
# app1 length.
|
||||
self.tiff = f.read(app1DataLength - 8)
|
||||
f.close()
|
||||
|
||||
self.endian = self.tiff[0]
|
||||
if self.endian not in ('I', 'M'):
|
||||
raise ExifFormatException("Invalid endianess found: %s" % self.endian)
|
||||
|
||||
# Now navigate to the items of interest and get them.
|
||||
ifdStart = self.getValueAtLocation(4, 4)
|
||||
self.ifdSearch(ifdStart)
|
||||
|
||||
#---------------------------------------
|
||||
def imageDescription(self):
|
||||
"""Return image description tag contents or '' if not found."""
|
||||
|
||||
return self.tagsToFind[self.IMAGE_DESCRIPTION_TAG].strip('\x20\x00')
|
||||
|
||||
#---------------------------------------
|
||||
def copyright(self):
|
||||
"""Return copyright tag contents or '' if not found."""
|
||||
|
||||
return self.tagsToFind[self.COPYRIGHT_TAG].strip('\x20\x00')
|
||||
|
||||
#---------------------------------------
|
||||
def dateTimeOriginal(self, formatString = None):
|
||||
"""Pass in optional format string to get time.strftime style formatting,
|
||||
else get default exif format for date time string (without trailing nul).
|
||||
Returns '' if tag not found."""
|
||||
|
||||
# The datetime should end in nul, get rid of it.
|
||||
if formatString is None or not self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG]:
|
||||
return self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00')
|
||||
else:
|
||||
# This will only work if the datetime string is in the standard exif format (i.e., hasn't been altered).
|
||||
try:
|
||||
import time
|
||||
return time.strftime(formatString, time.strptime(self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00'), '%Y:%m:%d %H:%M:%S'))
|
||||
except:
|
||||
return self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00')
|
||||
|
||||
|
||||
#---------------------------------------
|
||||
def ifdSearch(self, ifdStart):
|
||||
numIfdEntries = self.getValueAtLocation(ifdStart, 2)
|
||||
tagsStart = ifdStart + 2
|
||||
for entryNum in range(numIfdEntries):
|
||||
# For my purposes, all files will have either no tags, or
|
||||
# only our tags of interest, so no need to waste time trying to
|
||||
# break out of the loop early.
|
||||
thisTagStart = tagsStart + 12 * entryNum
|
||||
tagId = self.getValueAtLocation(thisTagStart, 2)
|
||||
if tagId == self.EXIF_SUBIFD_TAG:
|
||||
# This is a special tag that points to another ifd. Our
|
||||
# date time original tag is in the sub ifd.
|
||||
self.ifdSearch(self.getTagValue(thisTagStart))
|
||||
elif tagId in self.tagsToFind:
|
||||
assert(not self.tagsToFind[tagId])
|
||||
self.tagsToFind[tagId] = self.getTagValue(thisTagStart)
|
||||
|
||||
#---------------------------------------
|
||||
def getValueAtLocation(self, offset, length):
|
||||
slice = self.tiff[offset:offset + length]
|
||||
if self.endian == 'I':
|
||||
val = self.s2n_intel(slice)
|
||||
else:
|
||||
val = self.s2n_motorola(slice)
|
||||
return val
|
||||
|
||||
#---------------------------------------
|
||||
def s2n_motorola(self, str):
|
||||
x = 0
|
||||
for c in str:
|
||||
x = (x << 8) | ord(c)
|
||||
return x
|
||||
|
||||
#---------------------------------------
|
||||
def s2n_intel(self, str):
|
||||
x = 0
|
||||
y = 0
|
||||
for c in str:
|
||||
x = x | (ord(c) << y)
|
||||
y = y + 8
|
||||
return x
|
||||
|
||||
#---------------------------------------
|
||||
def getTagValue(self, thisTagStart):
|
||||
datatype = self.getValueAtLocation(thisTagStart + 2, 2)
|
||||
numBytes = [ 1, 1, 2, 4, 8, 1, 1, 2, 4, 8 ] [datatype-1] * self.getValueAtLocation(thisTagStart + 4, 4)
|
||||
if numBytes > 4:
|
||||
offsetToValue = self.getValueAtLocation(thisTagStart + 8, 4)
|
||||
return self.tiff[offsetToValue:offsetToValue + numBytes]
|
||||
else:
|
||||
if datatype == 2 or datatype == 1 or datatype == 7:
|
||||
return self.tiff[thisTagStart + 8:thisTagStart + 8 + numBytes]
|
||||
else:
|
||||
return self.getValueAtLocation(thisTagStart + 8, numBytes)
|
||||
|
||||
#---------------------------------------
|
||||
def __str__(self):
|
||||
return str(self.tagsToFind)
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if len(sys.argv) == 1:
|
||||
print "Pass jpgs to process."
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
for filename in sys.argv[1:]:
|
||||
try:
|
||||
f = MinimalExifReader(filename)
|
||||
print filename
|
||||
print "description: '%s'" % f.imageDescription()
|
||||
print "copyright: '%s'" % f.copyright()
|
||||
print "dateTimeOriginal: '%s'" % f.dateTimeOriginal()
|
||||
print "dateTimeOriginal: '%s'" % f.dateTimeOriginal('%B %d, %Y %I:%M:%S %p')
|
||||
print
|
||||
except ExifFormatException, ex:
|
||||
sys.stderr.write("Exif format error: %s\n" % ex)
|
||||
except:
|
||||
sys.stderr.write("Unable to process %s\n" % filename)
|
||||
|
457
stegano/exif/minimal_exif_writer.py
Normal file
457
stegano/exif/minimal_exif_writer.py
Normal file
|
@ -0,0 +1,457 @@
|
|||
"""
|
||||
Offers one class, MinimalExifWriter, which takes a jpg filename
|
||||
in the constructor. Allows you to: remove exif section, add
|
||||
image description, add copyright. Typical usage:
|
||||
|
||||
f = MinimalExifWriter('xyz.jpg')
|
||||
f.newImageDescription('This is a photo of something very interesting!')
|
||||
f.newCopyright('Jose Blow, All Rights Reserved', addCopyrightYear = 1)
|
||||
f.process()
|
||||
|
||||
Class methods:
|
||||
newImageDescription(description)--will add Exif ImageDescription to file.
|
||||
|
||||
newCopyright(copyright, addSymbol = 0, addYear = 0)--will add Exif Copyright to file.
|
||||
Will optionally prepend copyright symbol, or copyright symbol and current year.
|
||||
|
||||
removeExif()--will obliterate existing exif section.
|
||||
|
||||
process()--call after calling one or more of the above. Will remove existing exif
|
||||
section, optionally saving some existing tags (see below), and insert a new exif
|
||||
section with only three tags at most: description, copyright and date time original.
|
||||
If removeExif() not called, existing description (or new description if newDescription()
|
||||
called), existing copyright (or new copyright if newCopyright() called) and existing
|
||||
"DateTimeOriginal" (date/time picture taken) tags will be rewritten to the new
|
||||
minimal exif section.
|
||||
|
||||
Run at comand line with no args to see command line usage.
|
||||
|
||||
Does not work on unix due to differences in mmap. Not sure what's up there--
|
||||
don't need it on unix!
|
||||
|
||||
Brought to you by Megabyte Rodeo Software.
|
||||
http://www.fetidcascade.com/pyexif.html
|
||||
"""
|
||||
|
||||
# Written by Chris Stromberger, 10/2004. Public Domain.
|
||||
# Last updated: 12/3/2004.
|
||||
|
||||
DUMP_TIFF = 0
|
||||
VERBOSE = 0
|
||||
if VERBOSE:
|
||||
import binascii
|
||||
|
||||
import mmap
|
||||
import sys
|
||||
import minimal_exif_reader
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
class ExifFormatException(Exception):
|
||||
pass
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
class MinimalExifWriter:
|
||||
SOI_MARKER = '\xff\xd8'
|
||||
APP0_MARKER = '\xff\xe0'
|
||||
APP1_MARKER = '\xff\xe1'
|
||||
|
||||
# Standard app0 segment that will work for all files. We hope.
|
||||
# Based on http://www.funducode.com/freec/Fileformats/format3/format3b.htm.
|
||||
APP0 = '\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00'
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.removeExifSection = 0
|
||||
self.description = None
|
||||
self.copyright = None
|
||||
self.dateTimeOriginal = None
|
||||
|
||||
#---------------------------------------------
|
||||
def newImageDescription(self, description):
|
||||
self.description = description
|
||||
|
||||
#---------------------------------------------
|
||||
def newCopyright(self, copyright, addSymbol = 0, addYear = 0):
|
||||
if addYear:
|
||||
import time
|
||||
year = time.localtime()[0]
|
||||
self.copyright = "\xa9 %s %s" % (year, copyright)
|
||||
elif addSymbol:
|
||||
self.copyright = "\xa9 %s" % copyright
|
||||
else:
|
||||
self.copyright = copyright
|
||||
|
||||
#---------------------------------------------
|
||||
def removeExif(self):
|
||||
self.removeExifSection = 1
|
||||
|
||||
#---------------------------------------------
|
||||
def process(self):
|
||||
if not self.removeExifSection:
|
||||
self.getExistingExifInfo()
|
||||
|
||||
if VERBOSE:
|
||||
print self
|
||||
|
||||
import os
|
||||
try:
|
||||
fd = os.open(self.filename, os.O_RDWR)
|
||||
except:
|
||||
sys.stderr.write('Unable to open "%s"\n' % filename)
|
||||
return
|
||||
|
||||
self.m = mmap.mmap(fd, 0)
|
||||
os.close(fd)
|
||||
|
||||
# We only add app0 if all we're doing is removing the exif section.
|
||||
justRemovingExif = self.description is None and self.copyright is None and self.removeExifSection
|
||||
if VERBOSE: print 'justRemovingExif=%s' % justRemovingExif
|
||||
self.removeExifInfo(addApp0 = justRemovingExif)
|
||||
if justRemovingExif:
|
||||
self.m.close()
|
||||
return
|
||||
|
||||
# Get here means we are adding new description and/or copyright.
|
||||
self.removeApp0()
|
||||
|
||||
totalTagsToBeAdded = len(filter(None, (self.description, self.copyright, self.dateTimeOriginal)))
|
||||
assert(totalTagsToBeAdded > 0)
|
||||
|
||||
# Layout will be: firstifd|description|copyright|exififd|datetime.
|
||||
# First ifd will have tags: desc|copyright|subifd tag.
|
||||
ifd = [self.twoBytesHexIntel(totalTagsToBeAdded)]
|
||||
ifdEnd = ['\x00\x00\x00\x00']
|
||||
NUM_TAGS_LEN = 2
|
||||
TAG_LEN = 12
|
||||
NEXT_IFD_OFFSET_LEN = 4
|
||||
TIFF_HEADER_LENGTH = 8
|
||||
ifdLength = NUM_TAGS_LEN + TAG_LEN * totalTagsToBeAdded + NEXT_IFD_OFFSET_LEN
|
||||
|
||||
# Subifd only has one tag.
|
||||
SUBIFD_LENGTH = NUM_TAGS_LEN + TAG_LEN + NEXT_IFD_OFFSET_LEN
|
||||
|
||||
offsetToEndOfData = ifdLength + TIFF_HEADER_LENGTH
|
||||
|
||||
if self.description:
|
||||
ifd.append(self.descriptionTag(len(self.description), offsetToEndOfData))
|
||||
ifdEnd.append(self.description)
|
||||
offsetToEndOfData += len(self.description)
|
||||
|
||||
if self.copyright:
|
||||
ifd.append(self.copyrightTag(len(self.copyright), offsetToEndOfData))
|
||||
ifdEnd.append(self.copyright)
|
||||
offsetToEndOfData += len(self.copyright)
|
||||
|
||||
if self.dateTimeOriginal:
|
||||
ifd.append(self.subIfdTag(offsetToEndOfData))
|
||||
offsetToEndOfData += SUBIFD_LENGTH
|
||||
ifdEnd.append(self.buildSubIfd(len(self.dateTimeOriginal), offsetToEndOfData))
|
||||
ifdEnd.append(self.dateTimeOriginal)
|
||||
|
||||
app1 = self.buildApp1Section(ifd, ifdEnd)
|
||||
|
||||
self.addApp1(app1)
|
||||
|
||||
self.m.close()
|
||||
|
||||
#---------------------------------------------
|
||||
# Build exif subifd with one tag for datetime (0x9003).
|
||||
# Type is ascii (0x0002).
|
||||
def buildSubIfd(self, lenDateTime, offsetToEndOfData):
|
||||
return '\x01\x00\x03\x90\x02\x00%s%s\x00\x00\x00\x00' % (self.fourBytesHexIntel(lenDateTime), self.fourBytesHexIntel(offsetToEndOfData))
|
||||
|
||||
#---------------------------------------------
|
||||
def getExistingExifInfo(self):
|
||||
# Save off the old stuff.
|
||||
try:
|
||||
f = minimal_exif_reader.MinimalExifReader(self.filename)
|
||||
except:
|
||||
# Assume no existing exif info in the file. We
|
||||
# don't care.
|
||||
return
|
||||
|
||||
if not self.description:
|
||||
self.description = f.imageDescription()
|
||||
|
||||
if not self.copyright:
|
||||
self.copyright = f.copyright()
|
||||
|
||||
self.dateTimeOriginal = f.dateTimeOriginal()
|
||||
if self.dateTimeOriginal:
|
||||
# Restore ending nul.
|
||||
if self.dateTimeOriginal[-1] != '\x00':
|
||||
self.dateTimeOriginal += '\x00'
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def removeExifInfo(self, addApp0 = 1):
|
||||
"""Remove the app1 section of the jpg. This removes all exif info and the exif
|
||||
thumbnail. addApp0 should be 1 to add a minimal app0 section right after soi
|
||||
to make it a legitimate jpg, I think (various image programs can read the file
|
||||
without app0, but I think the standard requires one).
|
||||
"""
|
||||
# Read first bit of file to see if exif file.
|
||||
self.m.seek(0)
|
||||
if self.m.read(2) != self.SOI_MARKER:
|
||||
self.m.close()
|
||||
raise ExifFormatException("Missing SOI marker")
|
||||
|
||||
app0DataLength = 0
|
||||
appMarker = self.m.read(2)
|
||||
# See if there's an APP0 section, which sometimes appears.
|
||||
if appMarker == self.APP0_MARKER:
|
||||
if VERBOSE: print 'app0 found'
|
||||
app0DataLength = ord(self.m.read(1)) * 256 + ord(self.m.read(1))
|
||||
if VERBOSE: print 'app0DataLength: %s' % app0DataLength
|
||||
# Back up 2 bytes to get the length bytes.
|
||||
self.m.seek(-2, 1)
|
||||
existingApp0 = self.m.read(app0DataLength)
|
||||
appMarker = self.m.read(2)
|
||||
|
||||
if appMarker != self.APP1_MARKER:
|
||||
# We don't care, we'll add our minimal app1 later.
|
||||
return
|
||||
|
||||
exifHeader = self.m.read(8)
|
||||
if VERBOSE: print 'exif header: %s' % binascii.hexlify(exifHeader)
|
||||
if (exifHeader[2:6] != 'Exif' or
|
||||
exifHeader[6:8] != '\x00\x00'):
|
||||
self.m.close()
|
||||
raise ExifFormatException("Malformed APP1")
|
||||
|
||||
app1Length = ord(exifHeader[0]) * 256 + ord(exifHeader[1])
|
||||
if VERBOSE: print 'app1Length: %s' % app1Length
|
||||
|
||||
originalFileSize = self.m.size()
|
||||
|
||||
# Shift stuff just past app1 to overwrite app1.
|
||||
# Start at app1 length bytes in + other bytes not incl in app1 length.
|
||||
src = app1Length + len(self.SOI_MARKER) + len(self.APP1_MARKER)
|
||||
if app0DataLength:
|
||||
src += app0DataLength + len(self.APP0_MARKER)
|
||||
dest = len(self.SOI_MARKER)
|
||||
if addApp0:
|
||||
if app0DataLength != 0:
|
||||
# We'll re-add the existing app0.
|
||||
dest += app0DataLength + len(self.APP0_MARKER)
|
||||
else:
|
||||
# Add our generic app0.
|
||||
dest += len(self.APP0)
|
||||
count = originalFileSize - app1Length - len(self.SOI_MARKER) - len(self.APP1_MARKER)
|
||||
if app0DataLength:
|
||||
count -= app0DataLength + len(self.APP0_MARKER)
|
||||
|
||||
if VERBOSE: print 'self.m.move(%s, %s, %s)' % (dest, src, count)
|
||||
self.m.move(dest, src, count)
|
||||
|
||||
if addApp0:
|
||||
if app0DataLength != 0:
|
||||
self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER))
|
||||
else:
|
||||
self.m.seek(len(self.SOI_MARKER))
|
||||
self.m.write(self.APP0)
|
||||
self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER) + len(self.APP0))
|
||||
else:
|
||||
self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER))
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def removeApp0(self):
|
||||
self.m.seek(0)
|
||||
header = self.m.read(6)
|
||||
if (header[0:2] != self.SOI_MARKER or
|
||||
header[2:4] != self.APP0_MARKER):
|
||||
if VERBOSE: print 'no app0 found: %s' % binascii.hexlify(header)
|
||||
return
|
||||
|
||||
originalFileSize = self.m.size()
|
||||
|
||||
app0Length = ord(header[4]) * 256 + ord(header[5])
|
||||
if VERBOSE: print 'app0Length:', app0Length
|
||||
|
||||
# Shift stuff to overwrite app0.
|
||||
# Start at app0 length bytes in + other bytes not incl in app0 length.
|
||||
src = app0Length + len(self.SOI_MARKER) + len(self.APP0_MARKER)
|
||||
dest = len(self.SOI_MARKER)
|
||||
count = originalFileSize - app0Length - len(self.SOI_MARKER) - len(self.APP0_MARKER)
|
||||
self.m.move(dest, src, count)
|
||||
if VERBOSE: print 'm.move(%s, %s, %s)' % (dest, src, count)
|
||||
self.m.resize(originalFileSize - app0Length - len(self.APP0_MARKER))
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def addApp1(self, app1):
|
||||
originalFileSize = self.m.size()
|
||||
|
||||
# Insert app1 section.
|
||||
self.m.resize(originalFileSize + len(app1))
|
||||
src = len(self.SOI_MARKER)
|
||||
dest = len(app1) + len(self.SOI_MARKER)
|
||||
count = originalFileSize - len(self.SOI_MARKER)
|
||||
self.m.move(dest, src, count)
|
||||
self.m.seek(len(self.SOI_MARKER))
|
||||
self.m.write(app1)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def fourBytesHexIntel(self, number):
|
||||
return '%s%s%s%s' % (chr(number & 0x000000ff),
|
||||
chr((number >> 8) & 0x000000ff),
|
||||
chr((number >> 16) & 0x000000ff),
|
||||
chr((number >> 24) & 0x000000ff))
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def twoBytesHexIntel(self, number):
|
||||
return '%s%s' % (chr(number & 0x00ff),
|
||||
chr((number >> 8) & 0x00ff))
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def descriptionTag(self, numChars, loc):
|
||||
return self.asciiTag('\x0e\x01', numChars, loc)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def copyrightTag(self, numChars, loc):
|
||||
return self.asciiTag('\x98\x82', numChars, loc)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def subIfdTag(self, loc):
|
||||
return '\x69\x87\x04\x00\x01\x00\x00\x00%s' % self.fourBytesHexIntel(loc)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def asciiTag(self, tag, numChars, loc):
|
||||
"""Create ascii tag. Assumes description > 4 chars long."""
|
||||
|
||||
return '%s\x02\x00%s%s' % (tag, self.fourBytesHexIntel(numChars), self.fourBytesHexIntel(loc))
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def buildApp1Section(self, ifdPieces, ifdEndPieces):
|
||||
"""Create the APP1 section of an exif jpg. Consists of exif header plus
|
||||
tiff header + ifd and associated data."""
|
||||
|
||||
# Intel byte order, offset to first ifd will be 8.
|
||||
tiff = 'II\x2a\x00\x08\x00\x00\x00%s%s' % (''.join(ifdPieces), ''.join(ifdEndPieces))
|
||||
if DUMP_TIFF:
|
||||
f = open('tiff.dump', 'wb')
|
||||
f.write(tiff)
|
||||
f.close()
|
||||
app1Length = len(tiff) + 8
|
||||
return '\xff\xe1%s%sExif\x00\x00%s' % (chr((app1Length >> 8) & 0x00ff), chr(app1Length & 0x00ff), tiff)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def __str__(self):
|
||||
return """filename: %(filename)s
|
||||
removeExifSection: %(removeExifSection)s
|
||||
description: %(description)s
|
||||
copyright: %(copyright)s
|
||||
dateTimeOriginal: %(dateTimeOriginal)s
|
||||
""" % self.__dict__
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def usage(error = None):
|
||||
"""Print command line usage and exit"""
|
||||
|
||||
if error:
|
||||
print error
|
||||
print
|
||||
|
||||
print """This program will remove exif info from an exif jpg, and can optionally
|
||||
add the ImageDescription exif tag and/or the Copyright tag. But it will always remove
|
||||
some or all existing exif info (depending on options--see below)!
|
||||
So don't run this on your original images without a backup.
|
||||
|
||||
Options:
|
||||
-h: shows this message.
|
||||
-f <file>: jpg to process (required).
|
||||
-x: remove exif info (including thumbnail).
|
||||
-d <description or file>: remove exif info (including thumbnail) and then add exif
|
||||
ImageDescription. Will save the existing copyright tag if present,
|
||||
as well as the date time original tag (date & time photo taken),
|
||||
unless -x also passed (-x always means remove all exif info).
|
||||
It will attempt to open whatever is passed on the
|
||||
command line as a file; if successful, the contents of the file
|
||||
are added as the description, else the literal text on the
|
||||
command line is used as the description.
|
||||
-c <copyright or file>: remove exif info (including thumbnail) and then add exif
|
||||
Copyright tag. Will save the existing image description tag if present,
|
||||
as well as the date time original tag (date & time photo taken),
|
||||
unless -x also passed (-x always means remove all exif info).
|
||||
It will attempt to open whatever is passed on the command line as a file;
|
||||
if successful, the contents of the file are added as the copyright,
|
||||
else the literal text on the command line is used as the copyright.
|
||||
-s: prepend copyright symbol to copyright.
|
||||
-y: prepend copyright symbol and current year to copyright.
|
||||
|
||||
The image description and copyright must be > 4 characters long.
|
||||
|
||||
This software courtesy of Megabyte Rodeo Software."""
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
def parseArgs(args_):
|
||||
import getopt
|
||||
try:
|
||||
opts, args = getopt.getopt(args_, "yshxd:f:c:")
|
||||
except getopt.GetoptError:
|
||||
usage()
|
||||
|
||||
filename = None
|
||||
description = ''
|
||||
copyright = ''
|
||||
addCopyrightSymbol = 0
|
||||
addCopyrightYear = 0
|
||||
removeExif = 0
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage()
|
||||
if o == "-f":
|
||||
filename = a
|
||||
if o == "-d":
|
||||
try:
|
||||
f = open(a)
|
||||
description = f.read()
|
||||
f.close()
|
||||
except:
|
||||
description = a
|
||||
if o == "-c":
|
||||
try:
|
||||
f = open(a)
|
||||
copyright = f.read()
|
||||
f.close()
|
||||
except:
|
||||
copyright = a
|
||||
if o == '-x':
|
||||
removeExif = 1
|
||||
if o == '-s':
|
||||
addCopyrightSymbol = 1
|
||||
if o == '-y':
|
||||
addCopyrightYear = 1
|
||||
|
||||
if filename is None:
|
||||
usage('Missing jpg filename')
|
||||
if description and (len(description) <= 4 or len(description) > 60000):
|
||||
usage('Description too short or too long')
|
||||
if copyright and (len(copyright) <= 4 or len(copyright) > 60000):
|
||||
usage('Copyright too short or too long')
|
||||
if not description and not copyright and not removeExif:
|
||||
usage('Nothing to do!')
|
||||
|
||||
return filename, description, copyright, removeExif, addCopyrightSymbol, addCopyrightYear
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
filename, description, copyright, removeExif, addCopyrightSymbol, addCopyrightYear = parseArgs(sys.argv[1:])
|
||||
f = MinimalExifWriter(filename)
|
||||
if description:
|
||||
f.newImageDescription(description)
|
||||
if copyright:
|
||||
f.newCopyright(copyright, addCopyrightSymbol, addCopyrightYear)
|
||||
if removeExif:
|
||||
f.removeExif()
|
||||
|
||||
f.process()
|
||||
except ExifFormatException, ex:
|
||||
sys.stderr.write("Exif format error: %s\n" % ex)
|
||||
except SystemExit:
|
||||
pass
|
||||
except:
|
||||
sys.stderr.write("Unable to process %s\n" % filename)
|
||||
raise
|
118
stegano/exifHeader.py
Normal file
118
stegano/exifHeader.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
|
||||
#
|
||||
# For more information : http://bitbucket.org/cedricbonhomme/stegano/
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2010/03/24 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
# Thanks to: http://www.julesberman.info/spec2img.htm
|
||||
|
||||
def hide(img, img_enc, copyright="http://bitbucket.org/cedricbonhomme/stegano", \
|
||||
secret_message = None, secret_file = None):
|
||||
"""
|
||||
"""
|
||||
import shutil
|
||||
import datetime
|
||||
from zlib import compress
|
||||
from zlib import decompress
|
||||
from base64 import b64encode
|
||||
from exif.minimal_exif_writer import MinimalExifWriter
|
||||
|
||||
if secret_file != None:
|
||||
with open(secret_file, "r") as f:
|
||||
secret_file_content = f.read()
|
||||
text = "\nImage annotation date: "
|
||||
text = text + str(datetime.date.today())
|
||||
text = text + "\nImage description:\n"
|
||||
if secret_file != None:
|
||||
text = compress(b64encode(text + secret_file_content))
|
||||
else:
|
||||
text = compress(b64encode(text + secret_message))
|
||||
|
||||
try:
|
||||
shutil.copy(img, img_enc)
|
||||
except Exception as e:
|
||||
print("Impossible to copy image:", e)
|
||||
return
|
||||
|
||||
f = MinimalExifWriter(img_enc)
|
||||
f.removeExif()
|
||||
f.newImageDescription(text)
|
||||
f.newCopyright(copyright, addYear = 1)
|
||||
f.process()
|
||||
|
||||
|
||||
def reveal(img):
|
||||
"""
|
||||
"""
|
||||
from base64 import b64decode
|
||||
from zlib import decompress
|
||||
from exif.minimal_exif_reader import MinimalExifReader
|
||||
try:
|
||||
g = MinimalExifReader(img)
|
||||
except:
|
||||
print("Impossible to read description.")
|
||||
return
|
||||
print(b64decode(decompress(g.imageDescription())))
|
||||
print(("\nCopyright " + g.copyright()))
|
||||
#print g.dateTimeOriginal()s
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Point of entry in execution mode.
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(version=__version__)
|
||||
parser.add_option('--hide', action='store_true', default=False,
|
||||
help="Hides a message in an image.")
|
||||
parser.add_option('--reveal', action='store_true', default=False,
|
||||
help="Reveals the message hided in an image.")
|
||||
# Original image
|
||||
parser.add_option("-i", "--input", dest="input_image_file",
|
||||
help="Input image file.")
|
||||
# Image containing the secret
|
||||
parser.add_option("-o", "--output", dest="output_image_file",
|
||||
help="Output image containing the secret.")
|
||||
|
||||
# Secret raw message to hide
|
||||
parser.add_option("-m", "--secret-message", dest="secret_message",
|
||||
help="Your raw secret message to hide.")
|
||||
|
||||
# Secret text file to hide.
|
||||
parser.add_option("-f", "--secret-file", dest="secret_file",
|
||||
help="Your secret textt file to hide.")
|
||||
|
||||
parser.set_defaults(input_image_file = './pictures/Elisha-Cuthbert.jpg',
|
||||
output_image_file = './pictures/Elisha-Cuthbert_enc.jpg',
|
||||
secret_message = '', secret_file = '')
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.hide:
|
||||
if options.secret_message != "" and options.secret_file == "":
|
||||
hide(img=options.input_image_file, img_enc=options.output_image_file, \
|
||||
secret_message=options.secret_message)
|
||||
elif options.secret_message == "" and options.secret_file != "":
|
||||
hide(img=options.input_image_file, img_enc=options.output_image_file, \
|
||||
secret_file=options.secret_file)
|
||||
|
||||
elif options.reveal:
|
||||
reveal(img=options.input_image_file)
|
154
stegano/slsb.py
Executable file
154
stegano/slsb.py
Executable file
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
|
||||
#
|
||||
# For more information : http://bitbucket.org/cedricbonhomme/stegano/
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.2 $"
|
||||
__date__ = "$Date: 2010/03/24 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
import tools
|
||||
|
||||
def hide(img, message):
|
||||
"""
|
||||
Hide a message (string) in an image with the
|
||||
LSB (Least Significant Bit) technique.
|
||||
"""
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
index = 0
|
||||
|
||||
message = str(len(message)) + ":" + message
|
||||
#message_bits = tools.a2bits(message)
|
||||
message_bits = "".join(tools.a2bits_list(message))
|
||||
|
||||
npixels = width * height
|
||||
if len(message_bits) > npixels * 3:
|
||||
return """Too long message (%s > %s).""" % (len(message_bits), npixels * 3)
|
||||
|
||||
for row in xrange(height):
|
||||
for col in xrange(width):
|
||||
|
||||
if index + 3 <= len(message_bits) :
|
||||
|
||||
# Get the colour component.
|
||||
(r, g, b) = img.getpixel((col, row))
|
||||
|
||||
# Change the Least Significant Bit of each colour component.
|
||||
r = tools.setlsb(r, message_bits[index])
|
||||
g = tools.setlsb(g, message_bits[index+1])
|
||||
b = tools.setlsb(b, message_bits[index+2])
|
||||
|
||||
# Save the new pixel
|
||||
encoded.putpixel((col, row), (r, g , b))
|
||||
|
||||
index += 3
|
||||
|
||||
return encoded
|
||||
|
||||
def reveal(img):
|
||||
"""
|
||||
Find a message in an image
|
||||
(with the LSB technique).
|
||||
"""
|
||||
width, height = img.size
|
||||
buff, count = 0, 0
|
||||
bitab = []
|
||||
limit = None
|
||||
for row in xrange(height):
|
||||
for col in xrange(width):
|
||||
|
||||
# color = [r, g, b]
|
||||
for color in img.getpixel((col, row)):
|
||||
buff += (color&1)<<(7-count)
|
||||
count += 1
|
||||
if count == 8:
|
||||
bitab.append(chr(buff))
|
||||
buff, count = 0, 0
|
||||
if bitab[-1] == ":" and limit == None:
|
||||
try:
|
||||
limit = int("".join(bitab[:-1]))
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(bitab)-len(str(limit))-1 == limit :
|
||||
return "".join(bitab)[len(str(limit))+1:]
|
||||
return ""
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Point of entry in execution mode.
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(version=__version__)
|
||||
parser.add_option('--hide', action='store_true', default=False,
|
||||
help="Hides a message in an image.")
|
||||
parser.add_option('--reveal', action='store_true', default=False,
|
||||
help="Reveals the message hided in an image.")
|
||||
# Original image
|
||||
parser.add_option("-i", "--input", dest="input_image_file",
|
||||
help="Input image file.")
|
||||
# Image containing the secret
|
||||
parser.add_option("-o", "--output", dest="output_image_file",
|
||||
help="Output image containing the secret.")
|
||||
|
||||
# Non binary secret message to hide
|
||||
parser.add_option("-m", "--secret-message", dest="secret_message",
|
||||
help="Your secret message to hide (non binary).")
|
||||
|
||||
# Binary secret to hide (OGG, executable, etc.)
|
||||
parser.add_option("-f", "--secret-file", dest="secret_file",
|
||||
help="Your secret to hide (Text or any binary file).")
|
||||
# Output for the binary binary secret.
|
||||
parser.add_option("-b", "--binary", dest="secret_binary",
|
||||
help="Output for the binary secret (Text or any binary file).")
|
||||
|
||||
parser.set_defaults(input_image_file = './pictures/Lenna.png',
|
||||
output_image_file = './pictures/Lenna_enc.png',
|
||||
secret_message = '', secret_file = '', secret_binary = "")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
|
||||
if options.hide:
|
||||
if options.secret_message != "" and options.secret_file == "":
|
||||
secret = options.secret_message
|
||||
elif options.secret_message == "" and options.secret_file != "":
|
||||
secret = tools.binary2base64(options.secret_file)
|
||||
|
||||
img = Image.open(options.input_image_file)
|
||||
img_encoded = hide(img, secret)
|
||||
try:
|
||||
img_encoded.save(options.output_image_file)
|
||||
except Exception, e:
|
||||
# If hide() returns an error (Too long message).
|
||||
print e
|
||||
|
||||
elif options.reveal:
|
||||
img = Image.open(options.input_image_file)
|
||||
secret = reveal(img)
|
||||
if options.secret_binary != "":
|
||||
data = tools.base642binary(secret)
|
||||
with open(options.secret_binary, "w") as f:
|
||||
f.write(data)
|
||||
else:
|
||||
print secret
|
68
stegano/steganalysis-parity.py
Normal file
68
stegano/steganalysis-parity.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
|
||||
#
|
||||
# For more information : http://bitbucket.org/cedricbonhomme/stegano/
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2010/10/01 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PIL import Image
|
||||
|
||||
def steganalyse(img):
|
||||
"""
|
||||
Steganlysis of the LSB technique.
|
||||
"""
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
bits = ""
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
r, g, b = img.getpixel((col, row))
|
||||
if r % 2 == 0:
|
||||
r = 0
|
||||
else:
|
||||
r = 255
|
||||
if g % 2 == 0:
|
||||
g = 0
|
||||
else:
|
||||
g = 255
|
||||
if b % 2 == 0:
|
||||
b = 0
|
||||
else:
|
||||
b = 255
|
||||
encoded.putpixel((col, row), (r, g , b))
|
||||
return encoded
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Point of entry in execution mode.
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser()
|
||||
parser.add_option("-i", "--input", dest="input_image_file",
|
||||
help="Image file")
|
||||
parser.add_option("-o", "--output", dest="output_image_file",
|
||||
help="Image file")
|
||||
parser.set_defaults(input_image_file = './pictures/Lenna.png',
|
||||
output_image_file = './pictures/Lenna_steganalysed.png')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
input_image_file = Image.open(options.input_image_file)
|
||||
output_image = steganalyse(input_image_file)
|
||||
output_image.save(options.output_image_file)
|
70
stegano/steganalysis-statistics.py
Normal file
70
stegano/steganalysis-statistics.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
|
||||
#
|
||||
# For more information : http://bitbucket.org/cedricbonhomme/stegano/
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2010/10/01 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import operator
|
||||
|
||||
from PIL import Image
|
||||
from collections import Counter
|
||||
from collections import OrderedDict
|
||||
|
||||
def steganalyse(img):
|
||||
"""
|
||||
Steganlysis of the LSB technique.
|
||||
"""
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
colours = Counter()
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
r, g, b = img.getpixel((col, row))
|
||||
colours[r] += 1
|
||||
|
||||
most_common = colours.most_common(10)
|
||||
dict_colours = OrderedDict(sorted(colours.items(), key=lambda t: t[1]))
|
||||
|
||||
colours = 0
|
||||
for colour in dict_colours.keys():
|
||||
colours += colour
|
||||
colours = colours / len(dict_colours)
|
||||
|
||||
#return colours.most_common(10)
|
||||
return dict_colours.keys()[:30], most_common
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Point of entry in execution mode.
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser()
|
||||
parser.add_option("-i", "--input", dest="input_image_file",
|
||||
help="Image file.")
|
||||
parser.add_option("-o", "--output", dest="output_image_file",
|
||||
help="Image file.")
|
||||
parser.set_defaults(input_image_file = './pictures/Lenna.png',
|
||||
output_image_file = './pictures/Lenna_steganalysed.png')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
input_image_file = Image.open(options.input_image_file)
|
||||
output_image = steganalyse(input_image_file)
|
||||
soutput_image.save(options.output_image_file)
|
109
stegano/tools.py
Executable file
109
stegano/tools.py
Executable file
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
|
||||
#
|
||||
# For more information : http://bitbucket.org/cedricbonhomme/stegano/
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2010/10/01 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import base64
|
||||
|
||||
def a2bits(chars):
|
||||
"""
|
||||
Converts a string to its bits representation as a string of 0's and 1's.
|
||||
|
||||
>>> a2bits("Hello World!")
|
||||
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
||||
"""
|
||||
return bin(reduce(lambda x, y : (x<<8)+y, (ord(c) for c in chars), 1))[3:]
|
||||
|
||||
def a2bits_list(chars):
|
||||
"""
|
||||
Convert a string to its bits representation as a list of 0's and 1's.
|
||||
|
||||
>>> a2bits_list("Hello World!")
|
||||
['01001000',
|
||||
'01100101',
|
||||
'01101100',
|
||||
'01101100',
|
||||
'01101111',
|
||||
'00100000',
|
||||
'01010111',
|
||||
'01101111',
|
||||
'01110010',
|
||||
'01101100',
|
||||
'01100100',
|
||||
'00100001']
|
||||
>>> "".join(a2bits_list("Hello World!"))
|
||||
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
||||
"""
|
||||
return [bin(ord(x))[2:].rjust(8,"0") for x in chars]
|
||||
|
||||
def bs(s):
|
||||
"""
|
||||
Converts an int to its bits representation as a string of 0's and 1's.
|
||||
"""
|
||||
return str(s) if s<=1 else bs(s>>1) + str(s&1)
|
||||
|
||||
def setlsb(component, bit):
|
||||
"""
|
||||
Set Least Significant Bit of a colour component.
|
||||
"""
|
||||
return component & ~1 | int(bit)
|
||||
|
||||
def n_at_a_time(items, n, fillvalue):
|
||||
"""
|
||||
Returns an iterator which groups n items at a time.
|
||||
Any final partial tuple will be padded with the fillvalue
|
||||
|
||||
>>> list(n_at_a_time([1, 2, 3, 4, 5], 2, 'X'))
|
||||
[(1, 2), (3, 4), (5, 'X')]
|
||||
"""
|
||||
it = iter(items)
|
||||
return its.izip_longest(*[it] * n, fillvalue=fillvalue)
|
||||
|
||||
def binary2base64(binary_file):
|
||||
"""
|
||||
Convert a binary file (OGG, executable, etc.) to a
|
||||
printable string.
|
||||
"""
|
||||
# Use mode = "rb" to read binary file
|
||||
fin = open(binary_file, "rb")
|
||||
binary_data = fin.read()
|
||||
fin.close()
|
||||
|
||||
# Encode binary to base64 string (printable)
|
||||
return base64.b64encode(binary_data)
|
||||
|
||||
"""fout = open(output_file, "w")
|
||||
fout.write(b64_data)
|
||||
fout.close"""
|
||||
|
||||
def base642binary(b64_fname):
|
||||
"""
|
||||
Convert a printable file to a binary file.
|
||||
"""
|
||||
# Read base64 string
|
||||
#fin = open(b64_fname, "r")
|
||||
#b64_str = fin.read()
|
||||
#fin.close()
|
||||
# Decode base64 string to original binary sound object
|
||||
return base64.b64decode(b64_fname)
|
Loading…
Add table
Add a link
Reference in a new issue