diff --git a/jpegObj/__init__.py b/jpegObj/__init__.py index 0b12853..297376e 100644 --- a/jpegObj/__init__.py +++ b/jpegObj/__init__.py @@ -30,6 +30,8 @@ colorCode = { "YCCK": 5 } +colorParam = ['Y', 'Cb', 'Cr'] + # The JPEG class # ============== @@ -69,28 +71,35 @@ class Jpeg(Jsteg): # 1D Signal Representations # ------------------------- - def rawsignal(self, mask=base.acMaskBlock): + def rawsignal(self, mask=base.acMaskBlock, channel="All"): """ Return a 1D array of AC coefficients. (Most applications should use getsignal() rather than rawsignal().) """ R = [] - for X in self.coef_arrays: + if channel == "All": + for X in self.coef_arrays: + (h, w) = X.shape + A = base.acMask(h, w, mask) + R = np.hstack([R, X[A]]) + else: + cID = self.getCompID(channel) + X = self.coef_arrays[cID] (h, w) = X.shape A = base.acMask(h, w, mask) R = np.hstack([R, X[A]]) return R - def getsignal(self, mask=base.acMaskBlock): + def getsignal(self, mask=base.acMaskBlock, channel="All"): """Return a 1D array of AC coefficients in random order.""" - R = self.rawsignal(mask) + R = self.rawsignal(mask, channel) if self.key == None: return R else: rnd.seed(self.key) return R[rnd.permutation(len(R))] - def setsignal(self, R0, mask=base.acMaskBlock): + def setsignal(self, R0, mask=base.acMaskBlock, channel="All"): """Reinserts AC coefficients from getitem in the correct positions.""" if self.key != None: rnd.seed(self.key) @@ -100,13 +109,40 @@ class Jpeg(Jsteg): R[P] = R0 else: R = R0 - for X in self.coef_arrays: + if channel == "All": + for cID in range(3): + X = self.coef_arrays[cID] + s = X.size * 63 / 64 + (h, w) = X.shape + X[base.acMask(h, w, mask)] = R[fst:(fst + s)] + fst += s + + # Jset + blocks = self.getCoefBlocks(channel=colorParam[cID]) + xmax, ymax = self.Jgetcompdim(cID) + for y in range(ymax): + for x in range(xmax): + block = blocks[y, x] + self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) + + else: + cID = self.getCompID(channel) + X = self.coef_arrays[cID] s = X.size * 63 / 64 (h, w) = X.shape X[base.acMask(h, w, mask)] = R[fst:(fst + s)] fst += s + + # Jset + blocks = self.getCoefBlocks(channel) + xmax, ymax = self.Jgetcompdim(cID) + for y in range(ymax): + for x in range(xmax): + block = blocks[y, x] + self.Jsetblock(x, y, cID, bytearray(block.astype(np.int16))) + assert len(R) == fst - return; + # Histogram and Image Statistics # ------------------------------ @@ -247,12 +283,12 @@ class Jpeg(Jsteg): assert blocks.shape[-2:] == (8, 8), "block is expected of size (8,8)" cID = self.getCompID(channel) - vmax,hmax = blocks.shape[:2] + vmax, hmax = blocks.shape[:2] for i in range(vmax): for j in range(hmax): v, h = i * 8, j * 8 - self.coef_arrays[cID][v:v + 8, h:h + 8] = blocks[i,j] - self.Jsetblock(j, i, cID, bytearray(blocks[i,j].astype(np.int16))) + self.coef_arrays[cID][v:v + 8, h:h + 8] = blocks[i, j] + self.Jsetblock(j, i, cID, bytearray(blocks[i, j].astype(np.int16))) # Decompression # ------------- diff --git a/jpegObj/__init__.pyc b/jpegObj/__init__.pyc index 6d5efc9..e6e4406 100644 Binary files a/jpegObj/__init__.pyc and b/jpegObj/__init__.pyc differ diff --git a/msteg/StegBase.py b/msteg/StegBase.py index b920464..e970b28 100644 --- a/msteg/StegBase.py +++ b/msteg/StegBase.py @@ -1,15 +1,6 @@ -""" -This module implements a common base class of the steganographic -algorithms which embed data into JPEG files. -In order to run plugins inheriting from this class -you must build the rw_dct.so library which interfaces with libjpeg. -To do so, just run -% python setup.py build -in stegotool/util and copy the rw_dct.so -library (which can be found somewhere in the build-directory to -stegotool/util. - -""" +__author__ = 'chunk' + + import numpy as np import time import os @@ -21,6 +12,7 @@ import mjsteg import jpegObj from common import * +sample_key = [46812L, 20559L, 31360L, 16681L, 27536L, 39553L, 5427L, 63029L, 56572L, 36476L, 25695L, 61908L, 63014L, 5908L, 59816L, 56765L] class StegBase(object): """ @@ -36,12 +28,14 @@ class StegBase(object): self.cov_jpeg = None self.cov_data = None self.hid_data = None + self.key = None def _get_cov_data(self, img_path): """ Returns DCT coefficients of the cover image. """ self.cov_jpeg = jpegObj.Jpeg(img_path) + self.key = self.cov_jpeg.getkey() self.cov_data = self.cov_jpeg.getCoefBlocks() return self.cov_data @@ -150,7 +144,7 @@ class StegBase(object): float(emb_size) / cov_size)) # dummy functions to please pylint - def _raw_embed(self, cov_data, hid_data, status_begin=0): + def _raw_embed(self, cov_data, hid_data): pass def _raw_extract(self, steg_data, num_bits): diff --git a/msteg/StegBase.pyc b/msteg/StegBase.pyc index 2de9c1f..918b89d 100644 Binary files a/msteg/StegBase.pyc and b/msteg/StegBase.pyc differ diff --git a/msteg/steganography/F3.py b/msteg/steganography/F3.py index d42c541..cfbc781 100644 --- a/msteg/steganography/F3.py +++ b/msteg/steganography/F3.py @@ -1,14 +1,6 @@ -""" -

This module implements the F3 steganography algorithm invented by -Andreas Westfeld.

- -It embeds a secret message in JPEG DCT coefficients. -It differs from simpler algorithms such as JSteg by subtracting the -absolute DCT value if the currently embedded bit and the LSB of the DCT -value are not equal. DCT coefficients with value 0 are ignored.
-If a DCT coefficient is equal to 1 or -1 the current bit -is embedded repeatedly in order to make non-ambiguous extraction possible. -""" +__author__ = 'chunk' + + import time import math import numpy as np @@ -42,7 +34,7 @@ class F3(StegBase): lossy compression. """ self.t0 = time.time() - StegBase._post_embed_actions(self, src_cover, src_hidden, tgt_stego) + self._post_embed_actions(src_cover, src_hidden, tgt_stego) def extract_raw_data(self, src_steg, tgt_hidden): """ This method extracts secret data from a stego image. It is @@ -53,9 +45,9 @@ class F3(StegBase): tgt_hidden - A pathname denoting where the extracted data should be saved to. """ self.t0 = time.time() - StegBase._post_extract_actions(self, src_steg, tgt_hidden) + self._post_extract_actions(src_steg, tgt_hidden) - def _raw_embed(self, cov_data, hid_data, status_begin=0): + def _raw_embed(self, cov_data, hid_data): """ cov_data - 4-D numpy.int32 array hid_data - 1-D numpy.uint8 array diff --git a/msteg/steganography/F3.pyc b/msteg/steganography/F3.pyc index 07241ed..b3f2d1b 100644 Binary files a/msteg/steganography/F3.pyc and b/msteg/steganography/F3.pyc differ diff --git a/msteg/steganography/F4.py b/msteg/steganography/F4.py index 3fb0483..215324a 100644 --- a/msteg/steganography/F4.py +++ b/msteg/steganography/F4.py @@ -1,15 +1,11 @@ -""" -

This module implements a slight variant of the F4 steganography algorithm -invented by Andreas Westfeld. It embeds a secret message in JPEG -DCT coefficients.

-It differs from F3 in that even negative and odd positive DCT -coefficients represent a 1 and odd negative and even positive -DCT coefficients represent a 0. It also supports permutative strattling -which is not included in the original description of F4. -""" +__author__ = 'chunk' + import time import numpy as np -from msteg.StegBase import StegBase +import numpy.random as rnd +from msteg.StegBase import * +import mjsteg +import jpegObj from common import * @@ -18,51 +14,105 @@ class F4(StegBase): with the F3 algorithm and extract_raw_data to extract data which was embedded previously. """ - def __init__(self): + def __init__(self, key=sample_key): """ Constructor of the F3 class. """ StegBase.__init__(self) + self.key = key - def embed_raw_data(self, src_cover, src_hidden, tgt_stego): - """ This method embeds arbitrary data into a cover image. - The cover image must be a JPEG. + def _get_cov_data(self, img_path): + """ + Returns DCT coefficients of the cover image. + """ + self.cov_jpeg = jpegObj.Jpeg(img_path, key=self.key) - src_cover - A valid pathname to an image file which serves as cover image - (the image which the secret image is embedded into). + cov_data = self.cov_jpeg.getsignal(channel='Y') + self.cov_data = np.array(cov_data, dtype=np.int16) + return self.cov_data - src_hidden - A valid pathname to an arbitrary file that is supposed to be - embedded into the cover image. + def embed_raw_data(self, src_cover, src_hidden, tgt_stego): - tgt_stego - Target pathname of the resulting stego image. You should save to a - PNG or another lossless format, because many LSBs don't survive - lossy compression. - """ self.t0 = time.time() - StegBase._post_embed_actions(self, src_cover, src_hidden, tgt_stego) - def extract_raw_data(self, src_steg, tgt_hidden): - """ This method extracts secret data from a stego image. It is - (obviously) the inverse operation of embed_raw_data. + try: + cov_data = self._get_cov_data(src_cover) + hid_data = self._get_hid_data(src_hidden) + # print hid_data.dtype,type(hid_data),hid_data.tolist() - src_stego - A valid pathname to an image file which serves as stego image. + cov_data, bits_cnt = self._raw_embed(cov_data, hid_data) + + if bits_cnt != np.size(hid_data) * 8: + raise Exception("Expected embedded size is %db but actually %db." % ( + np.size(hid_data) * 8, bits_cnt)) + + self.cov_jpeg.setsignal(cov_data, channel='Y') + self.cov_jpeg.Jwrite(tgt_stego) + + # size_cov = os.path.getsize(tgt_stego) + size_cov = np.size(cov_data) / 8 + size_embedded = np.size(hid_data) + + self._display_stats("embedded", size_cov, size_embedded, + time.time() - self.t0) + + except TypeError as e: + raise e + except Exception as expt: + print "Exception when embedding!" + raise + + def extract_raw_data(self, src_steg, tgt_hidden): - tgt_hidden - A pathname denoting where the extracted data should be saved to. - """ self.t0 = time.time() - StegBase._post_extract_actions(self, src_steg, tgt_hidden) - def _raw_embed(self, cov_data, hid_data, status_begin=0): + try: + steg_data = self._get_cov_data(src_steg) + # emb_size = os.path.getsize(src_steg) + emb_size = np.size(steg_data) / 8 + + + # recovering file size + header_size = 4 * 8 + size_data, bits_cnt = self._raw_extract(steg_data, header_size) + size_data = bits2bytes(size_data) + print size_data + + size_hd = 0 + for i in xrange(4): + size_hd += size_data[i] * 256 ** i + + raw_size = size_hd * 8 + + if raw_size > np.size(steg_data): + raise Exception("Supposed secret data too large for stego image.") + + hid_data, bits_cnt = self._raw_extract(steg_data, raw_size) + + if bits_cnt != raw_size: + raise Exception("Expected embedded size is %db but actually %db." % ( + raw_size, bits_cnt)) + + hid_data = bits2bytes(hid_data) + # print hid_data.dtype,type(hid_data),hid_data.tolist() + hid_data[4:].tofile(tgt_hidden) + + self._display_stats("extracted", emb_size, + np.size(hid_data), + time.time() - self.t0) + except Exception as expt: + print "Exception when extracting!" + raise + + def _raw_embed(self, cov_data, hid_data): """ - cov_data - 4-D numpy.int32 array + cov_data - 1-D numpy.int16 array (permunated) hid_data - 1-D numpy.uint8 array """ hid_data = bytes2bits(hid_data) i = 0 - cnt = -1 for x in np.nditer(cov_data, op_flags=['readwrite']): - cnt = cnt + 1 - if x == 0 or cnt % 64 == 0: continue + if x == 0: continue m = (hid_data[i] & 1) if x > 0 and x & 1 != m: @@ -72,19 +122,17 @@ class F4(StegBase): if x == 0: continue i += 1 if i == hid_data.size: break - - return cov_data + return cov_data, i def _raw_extract(self, steg_data, num_bits): """ Just a small helper function to extract hidden data. + steg_data - 1-D numpy.int16 array (permunated) """ hid_data = np.zeros(num_bits, np.uint8) j = 0 - cnt = -1 - for x in np.nditer(steg_data): - cnt = cnt + 1 - if x == 0 or cnt % 64 == 0: continue + for x in steg_data: + if x == 0: continue if j >= num_bits: break if x > 0: hid_data[j] = x & 1 @@ -93,7 +141,7 @@ class F4(StegBase): j = j + 1 - return hid_data + return hid_data, j def __str__(self): return "F4'" diff --git a/msteg/steganography/F4.pyc b/msteg/steganography/F4.pyc index a4586e4..d1fc8c5 100644 Binary files a/msteg/steganography/F4.pyc and b/msteg/steganography/F4.pyc differ diff --git a/msteg/steganography/LSB.py b/msteg/steganography/LSB.py index 4bb097b..5be5721 100644 --- a/msteg/steganography/LSB.py +++ b/msteg/steganography/LSB.py @@ -1,12 +1,6 @@ -""" -

This plugin implements two variants of a well-known steganographic -procedure commonly referred to as LSB algorithm.

-The general idea is to -overwrite some portion (usually the k least significant bits) of each byte -in the cover image. The below methods specify the number of overwritten -bits a parameter named word_size. Thus --- in this context --- word means -\"a group of bits of a fixed size\". -""" +__author__ = 'chunk' + + import time import numpy as np import scipy as sp @@ -53,7 +47,7 @@ class LSB(StegBase): self.t0 = time.time() StegBase._post_extract_actions(self, src_steg, tgt_hidden) - def _raw_embed(self, cov_data, hid_data, status_begin=0): + def _raw_embed(self, cov_data, hid_data): """ cov_data - 4-D numpy.int32 array hid_data - 1-D numpy.uint8 array diff --git a/msteg/steganography/LSB.pyc b/msteg/steganography/LSB.pyc index 0c06f26..7cdbd23 100644 Binary files a/msteg/steganography/LSB.pyc and b/msteg/steganography/LSB.pyc differ diff --git a/res/steged.jpg b/res/steged.jpg index 7a84a41..f3f6c05 100644 Binary files a/res/steged.jpg and b/res/steged.jpg differ diff --git a/test_jpeg.py b/test_jpeg.py index 9996435..7a05a7f 100644 --- a/test_jpeg.py +++ b/test_jpeg.py @@ -3,6 +3,7 @@ __author__ = 'chunk' import numpy as np import mjsteg import jpegObj +from jpegObj import base from common import * timer = Timer() @@ -16,6 +17,7 @@ sample = [[7, 12, 14, -12, 1, 0, -1, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]] +sample_key = [46812L, 20559L, 31360L, 16681L, 27536L, 39553L, 5427L, 63029L, 56572L, 36476L, 25695L, 61908L, 63014L, 5908L, 59816L, 56765L] def diffblock(c1, c2): diff = False @@ -184,16 +186,28 @@ if __name__ == '__main__': # test_bitbyte() - ima = jpegObj.Jpeg("res/test3.jpg") - imb = jpegObj.Jpeg("res/steged.jpg") + ima = jpegObj.Jpeg("res/test3.jpg",key=sample_key) + imb = jpegObj.Jpeg("res/new.jpg",key=sample_key) + imc = jpegObj.Jpeg("res/steged.jpg",key=sample_key) print ima.Jgetcompdim(0) - diffblocks(ima, imb) - - c1 = ima.getCoefBlocks() - c2 = imb.getCoefBlocks() - - print c1[0],c2[0] + print ima.getkey(),imb.getkey() + diffblocks(imb, ima) + # c1 = ima.getCoefBlocks() + # c2 = imb.getCoefBlocks() + # + # # print c1[0],c2[0] + # s1 = imb.getsignal(channel='Y') + # s2 = ima.getsignal(channel='Y') + # imb.setsignal(s2,channel='Y') + # imb.Jwrite('res/new.jpg') + + + # print base.acMask(8,16) + # mmask = base.acMaskBlock + # print mmask + # sample = np.array(sample)[mmask] + # print np.hstack([[],sample]) pass diff --git a/test_steg.py b/test_steg.py index d7eb5bb..ab6c50d 100644 --- a/test_steg.py +++ b/test_steg.py @@ -22,10 +22,12 @@ sample = [[7, 12, 14, -12, 1, 0, -1, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]] +sample_key = [46812L, 20559L, 31360L, 16681L, 27536L, 39553L, 5427L, 63029L, 56572L, 36476L, 25695L, 61908L, 63014L, 5908L, 59816L, 56765L] + txtsample = [116, 104, 105, 115, 32, 105, 115, 32, 116, 111, 32, 98, 101, 32, 101, 109, 98, 101, 100, 101, 100, 46, 10] if __name__ == '__main__': - f3test = F3.F3() + f3test = F4.F4() f3test.embed_raw_data("res/test3.jpg", "res/embeded", "res/steged.jpg") f3test.extract_raw_data("res/steged.jpg", "res/extracted") -- libgit2 0.21.2