Commit c9fdeb00fb59fd89b2d92c58636b27ad186ea036

Authored by Chunk
1 parent 548d95dc
Exists in master

LSB and F4 added.

msteg/steganography/F3.py
... ... @@ -67,7 +67,7 @@ class F3(StegBase):
67 67 cnt = cnt + 1
68 68 if x == 0 or cnt % 64 == 0: continue
69 69  
70   - m = hid_data[i]
  70 + m = (hid_data[i] & 1)
71 71 if x & 1 != m:
72 72 x[...] -= math.copysign(1, x)
73 73 if x == 0: continue
... ...
msteg/steganography/F4.py 0 → 100644
... ... @@ -0,0 +1,99 @@
  1 +"""
  2 +<p>This module implements a slight variant of the F4 steganography algorithm
  3 +invented by Andreas Westfeld. It embeds a secret message in JPEG
  4 +DCT coefficients.</p>
  5 +It differs from F3 in that even negative and odd positive DCT
  6 +coefficients represent a 1 and odd negative and even positive
  7 +DCT coefficients represent a 0. It also supports permutative strattling
  8 +which is not included in the original description of F4.
  9 +"""
  10 +import time
  11 +import numpy as np
  12 +from StegBase import *
  13 +from common import *
  14 +
  15 +
  16 +class F3(StegBase):
  17 + """ This module has two methods: <i>embed_raw_data</i> to embed data
  18 + with the F3 algorithm and <i>extract_raw_data</i> to extract data
  19 + which was embedded previously. """
  20 +
  21 + def __init__(self):
  22 + """
  23 + Constructor of the F3 class.
  24 + """
  25 + StegBase.__init__(self)
  26 +
  27 + def embed_raw_data(self, src_cover, src_hidden, tgt_stego):
  28 + """ This method embeds arbitrary data into a cover image.
  29 + The cover image must be a JPEG.
  30 +
  31 + src_cover - A valid pathname to an image file which serves as cover image
  32 + (the image which the secret image is embedded into).
  33 +
  34 + src_hidden - A valid pathname to an arbitrary file that is supposed to be
  35 + embedded into the cover image.
  36 +
  37 + tgt_stego - Target pathname of the resulting stego image. You should save to a
  38 + PNG or another lossless format, because many LSBs don't survive
  39 + lossy compression.
  40 + """
  41 + self.t0 = time.time()
  42 + StegBase._post_embed_actions(self, src_cover, src_hidden, tgt_stego)
  43 +
  44 + def extract_raw_data(self, src_steg, tgt_hidden):
  45 + """ This method extracts secret data from a stego image. It is
  46 + (obviously) the inverse operation of embed_raw_data.
  47 +
  48 + src_stego - A valid pathname to an image file which serves as stego image.
  49 +
  50 + tgt_hidden - A pathname denoting where the extracted data should be saved to.
  51 + """
  52 + self.t0 = time.time()
  53 + StegBase._post_extract_actions(self, src_steg, tgt_hidden)
  54 +
  55 + def _raw_embed(self, cov_data, hid_data, status_begin=0):
  56 + """
  57 + cov_data - 4-D numpy.int32 array
  58 + hid_data - 1-D numpy.uint8 array
  59 + """
  60 + hid_data = bytes2bits(hid_data)
  61 + i = 0
  62 + cnt = -1
  63 + for x in np.nditer(cov_data, op_flags=['readwrite']):
  64 + cnt = cnt + 1
  65 + if x == 0 or cnt % 64 == 0: continue
  66 +
  67 + m = (hid_data[i] & 1)
  68 + if x > 0 and x & 1 != m:
  69 + x[...] -= 1
  70 + elif x < 0 and x & 1 == m:
  71 + x[...] += 1
  72 + if x == 0: continue
  73 + i += 1
  74 + if i == hid_data.size: break
  75 +
  76 + return cov_data
  77 +
  78 + def _raw_extract(self, steg_data, num_bits):
  79 + """
  80 + Just a small helper function to extract hidden data.
  81 + """
  82 + hid_data = np.zeros(num_bits, np.uint8)
  83 + j = 0
  84 + cnt = -1
  85 + for x in np.nditer(steg_data):
  86 + cnt = cnt + 1
  87 + if x == 0 or cnt % 64 == 0: continue
  88 + if j >= num_bits: break
  89 + if x > 0:
  90 + hid_data[j] = x & 1
  91 + else:
  92 + hid_data[j] = (x & 1) ^ 1
  93 +
  94 + j = j + 1
  95 +
  96 + return hid_data
  97 +
  98 + def __str__(self):
  99 + return "F4'"
... ...
msteg/steganography/F5.py 0 → 100644
... ... @@ -0,0 +1,372 @@
  1 +"""
  2 +<p>This module implements the rather sophisticated F5 algorithm which was
  3 +invented by Andreas Westfeld.</p>
  4 +
  5 +Unlike its vastly inferior predecessors, namely F3 and F4, it features matrix
  6 +encoding which makes it possible to embed a chunk of k bits within 2^k - 1
  7 +bits of the cover data and only change one bit (at most). A bit change is
  8 +done by subtracting the absolute value of the corresponding DCT coefficient.
  9 +When the embedding process begins, the parameter k is computed based on
  10 +the capacity of the cover image and the prospective embedding ratio.
  11 +With small amount of hidden data k becomes large which leads to a greater
  12 +embedding efficiency (embedded information per bit change).<br />
  13 +
  14 +A permutation (initialized by a user-supplied seed) of the DCT coefficients
  15 +helps to scatter each chunk across the entire image.
  16 +F5 can be seen as meta-algorithm as it uses a coding scheme to change
  17 +as little data as possible and then applies a simpler algorithm (such as F3)
  18 +to actually embed data. That is why this module allows the user to specify
  19 +which embedding function (one of JSteg, F3, F4) should be used.
  20 +"""
  21 +
  22 +import time
  23 +import math
  24 +import numpy as np
  25 +from stegotool.plugins.steganography.F4.F4 import F4
  26 +from stegotool.util.JPEGSteg import JPEGSteg
  27 +from stegotool.util.plugins import describe_annotate_convert
  28 +from stegotool.util.plugins import ident, ImagePath, FilePath, NewFilePath
  29 +
  30 +
  31 +class F5(JPEGSteg):
  32 + """ This module has two methods: <i>embed_raw_data</i> to embed data
  33 + with the F5 algorithm and <i>extract_raw_data</i> to extract data
  34 + which was embedded previously. """
  35 +
  36 + def __init__(self, ui, core):
  37 + """
  38 + Constructor of the F5 class.
  39 + """
  40 + JPEGSteg.__init__(self, ui, core)
  41 + self._embed_hook = self._embed_k
  42 + self._extract_hook = self._extract_k
  43 + self._embed_fun = None
  44 + self.dct_p = None
  45 + self.seed = None
  46 + self.default_embedding = True
  47 + self.steg_ind = -1
  48 + self.excess_bits = None
  49 + # needed because k is embedded separately
  50 + self.cov_ind = -1
  51 + self.k_coeff = -1
  52 +
  53 + @describe_annotate_convert((None, None, ident),
  54 + ("cover image", ImagePath, str),
  55 + ("hidden data", FilePath, str),
  56 + ("stego image", NewFilePath, str),
  57 + ("seed", int, int),
  58 + ("embedding behavior",
  59 + ['Default', 'F3', 'JSteg'], str))
  60 + def embed_raw_data(self, src_cover, src_hidden, tgt_stego, seed,
  61 + embed_fun):
  62 + """<p>This method embeds arbitrary data into a cover image.
  63 + The cover image must be a JPEG.</p>
  64 +
  65 + <p>Parameters:
  66 + <ol>
  67 + <li><pre>src_cover</pre>
  68 + A valid pathname to an image file which serves as cover image
  69 + (the image which the secret image is embedded into).</li>
  70 +
  71 + <li><pre>src_hidden</pre>
  72 + A valid pathname to an arbitrary file that is supposed to be
  73 + embedded into the cover image.</li>
  74 +
  75 + <li><pre>tgt_stego</pre>
  76 + Target pathname of the resulting stego image. You should save to
  77 + a PNG or another lossless format, because many LSBs don't survive
  78 + lossy compression.</li>
  79 +
  80 + <li><pre>seed</pre>
  81 + A seed for the random number generator that is responsible scattering
  82 + the secret data within the cover image.</li>
  83 +
  84 + <li><pre>param embed_fun</pre>
  85 + Specifies which embedding function should be used. Must be one of
  86 + 'Default', 'F3', 'Jsteg'. If 'Default' is selected, the algorithm uses
  87 + the same behavior as Westfeld's implementation, i.e. decrementing
  88 + absolute values for n > 1 (F3) and using F4 in the special case n = 1.
  89 + Selecting F3 or JSteg results in using that scheme for all n.</li>
  90 + </ol>
  91 + </p>
  92 + """
  93 + self.t0 = time.time()
  94 + self.seed = seed
  95 + if embed_fun == 'F3':
  96 + self._embed_fun = self._f3_embed
  97 + self.default_embedding = False
  98 + elif embed_fun == 'JSteg':
  99 + self._embed_fun = self._jsteg_embed
  100 + self.default_embedding = False
  101 + elif embed_fun == 'Default':
  102 + self._embed_fun = self._f3_embed
  103 + self.default_embedding = True
  104 +
  105 + self.cov_ind = -1
  106 + JPEGSteg._post_embed_actions(self, src_cover, src_hidden, tgt_stego)
  107 +
  108 + @describe_annotate_convert((None, None, ident),
  109 + ("stego image", ImagePath, str),
  110 + ("hidden data", NewFilePath, str),
  111 + ("seed", int, int),
  112 + ("embedding behavior", ['Default', 'F3/JSteg'],
  113 + str))
  114 + def extract_raw_data(self, src_steg, tgt_hidden, seed, embed_fun):
  115 + """<p>This method extracts secret data from a stego image. It is
  116 + (obviously) the inverse operation of embed_raw_data.</p>
  117 +
  118 + <p>Parameters:
  119 + <ol>
  120 + <li><pre>src_stego</pre>
  121 + A valid pathname to an image file which serves as stego image.</li>
  122 +
  123 + <li><pre>tgt_hidden</pre>
  124 + A pathname denoting where the extracted data should be saved to.</li>
  125 +
  126 + <li><pre>param seed</pre>
  127 + A seed for the random number generator that is responsible scattering
  128 + the secret data within the cover image.</li>
  129 +
  130 + <li><pre>param embed_fun</pre>
  131 + Specifies which embedding function should be used. Must be one of
  132 + 'Default', 'F3', 'JSteg'. If 'Default' is selected, the algorithm uses
  133 + the same behavior as Westfeld's implementation, i.e. decrementing
  134 + absolute values for n > 1 (F3) and using F4 in the special case n = 1.
  135 + Selecting F3 or JSteg results in using that scheme for all n.</li>
  136 + </ol></pre>
  137 + """
  138 +
  139 + self.t0 = time.time()
  140 + self.seed = seed
  141 + self.steg_ind = -1
  142 + if embed_fun == 'F3/JSteg':
  143 + self.default_embedding = False
  144 + elif embed_fun == 'Default':
  145 + self.default_embedding = True
  146 +
  147 + # excess bits occur when the size of extracted data is not a multiple
  148 + # of k. if excess bits are available, they are prepended to hidden data
  149 + self.excess_bits = None
  150 +
  151 + JPEGSteg._post_extract_actions(self, src_steg, tgt_hidden)
  152 +
  153 + def _embed_k(self, cov_data, hid_data):
  154 + np.random.seed(self.seed)
  155 + self.dct_p = np.random.permutation(cov_data.size)
  156 + self.k_coeff = self._find_max_k(cov_data, hid_data)
  157 + self.ui.display_status('setting k = %d' % self.k_coeff)
  158 + k_split = self.lookup_tab.split_byte(self.k_coeff, 1)[-4:]
  159 + # embed k in F3-like style
  160 + for m in k_split:
  161 + success = False
  162 + while not success:
  163 + self.cov_ind += 1
  164 + while cov_data[self.dct_p[self.cov_ind]] == 0 or \
  165 + self.dct_p[self.cov_ind] % 64 == 0:
  166 + self.cov_ind += 1
  167 + if m != cov_data[self.dct_p[self.cov_ind]] & 1:
  168 + cov_data[self.dct_p[self.cov_ind]] -= \
  169 + math.copysign(1, cov_data[self.dct_p[self.cov_ind]])
  170 + success = cov_data[self.dct_p[self.cov_ind]] != 0
  171 +
  172 + def _extract_k(self, steg_data):
  173 + # initializing the MT is done only once in order to retain the state
  174 + self.dct_p = np.random.seed(self.seed)
  175 + self.dct_p = np.random.permutation(self.steg_data.size)
  176 + k_split = np.zeros(4, np.uint8)
  177 + for i in xrange(k_split.size):
  178 + self.steg_ind += 1
  179 + while self.steg_data[self.dct_p[self.steg_ind]] == 0 or\
  180 + self.dct_p[self.steg_ind] % 64 == 0:
  181 + self.steg_ind += 1
  182 + k_split[i] = self.steg_data[self.dct_p[self.steg_ind]] & 1
  183 + self.k_coeff = self.lookup_tab.merge_words(tuple([0, 0, 0, 0] +
  184 + list(k_split)), 1)
  185 +
  186 + def _find_max_k(self, cov_data, hid_data):
  187 + cnt = 4 # information about k take up 4 bits
  188 + # find number of DCT coefficients
  189 + update_cnt = 10000
  190 + for i, c in enumerate(cov_data):
  191 + if update_cnt == 0:
  192 + self._set_progress(
  193 + int(30 * (float(i) / float(cov_data.size))))
  194 + update_cnt = 10000
  195 + update_cnt -= 1
  196 + # pessimistic, but accurate estimation of the capacity of the image
  197 + ci = int(c)
  198 + if (not (ci is 0)) and (not ((i % 64) is 0)) \
  199 + and (not (ci is 1)) and (not (ci is -1)):
  200 + cnt += 1
  201 + hid_size = hid_data.size
  202 + cov_size = cnt
  203 + if cov_size < hid_size:
  204 + raise Exception("Cannot fit %d bits in %d DCT coefficients. \
  205 + Cover image is too small." % (hid_size, cov_size))
  206 + self.ui.display_status('DCT embedding ratio = %f' \
  207 + % (float(hid_size) / float(cov_size)))
  208 + k = 1
  209 + while True:
  210 + k += 1
  211 + n = (1 << k) - 1
  212 + num_chunks = cov_size / n
  213 + num_emb_bits = num_chunks * k
  214 + if num_emb_bits < hid_size:
  215 + return min(k - 1, 15)
  216 +
  217 + # low level embedding functions
  218 + def _f3_embed(self, cov_data, ind):
  219 + cov_data[ind] -= math.copysign(1, cov_data[ind])
  220 +
  221 + def _jsteg_embed(self, cov_data, ind):
  222 + m = 1 ^ (cov_data[ind] & 1)
  223 + cov_data[ind] = (cov_data[ind] & 0xffffe) | m
  224 +
  225 + def _raw_embed(self, cov_data, hid_data, status_begin=0):
  226 + k = self.k_coeff
  227 + n = (1 << k) - 1
  228 + if n == 1 and self.default_embedding:
  229 + # in case k = n = 1, Westfeld's implementation uses F4 for
  230 + # embedding. Therefore, if 'default' embedding has been selected
  231 + # we will do the same
  232 + f4 = F4(self.ui, self.core)
  233 + f4.seed = self.seed
  234 + f4.dct_p = self.dct_p
  235 + f4.cov_ind = self.cov_ind
  236 + cov_data = f4._raw_embed(cov_data, hid_data, 30)
  237 + return cov_data
  238 +
  239 + cov_ind = self.cov_ind # preventing RSI by writing 'self' less often
  240 + hid_ind = 0
  241 + remaining_bits = hid_data.size
  242 + hid_size = float(hid_data.size)
  243 + dct_p = self.dct_p
  244 +
  245 + update_cnt = int(hid_size / (70.0 * k))
  246 + while remaining_bits > 0:
  247 + if update_cnt == 0:
  248 + self._set_progress(30 + int(((
  249 + hid_size - remaining_bits) / hid_size) * 70))
  250 + update_cnt = int(hid_size / (70.0 * k))
  251 + update_cnt -= 1
  252 + msg_chunk_size = min(remaining_bits, k)
  253 + msg_chunk = np.zeros(k, np.int8)
  254 + cov_chunk = np.zeros(n, np.int32)
  255 + msg_chunk[0:msg_chunk_size] = hid_data[hid_ind:hid_ind +
  256 + msg_chunk_size]
  257 + hid_ind += k
  258 +
  259 + # get n DCT coefficients
  260 + for i in xrange(n):
  261 + cov_ind += 1
  262 + while cov_data[dct_p[cov_ind]] == 0 \
  263 + or dct_p[cov_ind] % 64 == 0:
  264 + cov_ind += 1
  265 + cov_chunk[i] = dct_p[cov_ind]
  266 +
  267 + success = False
  268 + while not success: # loop necessary because of shrinkage
  269 + h = 0
  270 + for i in xrange(n):
  271 + h ^= ((cov_data[cov_chunk[i]] & 1) * (i + 1))
  272 + scalar_x = 0
  273 + for i in xrange(k):
  274 + scalar_x = (scalar_x << 1) + msg_chunk[i]
  275 + s = scalar_x ^ h
  276 + if s != 0:
  277 + self._embed_fun(cov_data, cov_chunk[s - 1])
  278 + else:
  279 + break
  280 +
  281 + if cov_data[cov_chunk[s - 1]] == 0: # test for shrinkage
  282 + cov_chunk[s - 1:-1] = cov_chunk[s:] # adjusting
  283 + cov_ind += 1
  284 + while cov_data[dct_p[cov_ind]] == 0 or\
  285 + dct_p[cov_ind] % 64 == 0:
  286 + cov_ind += 1
  287 + cov_chunk[n - 1] = dct_p[cov_ind]
  288 + else:
  289 + success = True
  290 +
  291 + remaining_bits -= k
  292 +
  293 + self.k_coeff = -1 # prevent k being read from this instance
  294 + return cov_data
  295 +
  296 + def _raw_extract(self, num_bits):
  297 + k = self.k_coeff
  298 + n = (1 << k) - 1
  299 + if self.is_header == None:
  300 + self.is_header = True
  301 + if n == 1 and self.default_embedding:
  302 + f4 = F4(self.ui, self.core)
  303 + f4.seed = self.seed
  304 + f4.dct_p = self.dct_p
  305 + f4.steg_data = self.steg_data
  306 + f4.is_header = self.is_header
  307 + f4.steg_ind = self.steg_ind
  308 + hid_data = f4._raw_extract(num_bits)
  309 + self.steg_ind = f4.steg_ind
  310 + self.is_header = False
  311 + return hid_data
  312 + remaining_bits = num_bits
  313 + hid_data = np.zeros(num_bits, np.uint8)
  314 + hid_ind = 0
  315 +
  316 + dct_p = self.dct_p
  317 +
  318 + is_header = False # signals whether or not extracting header
  319 +
  320 + if self.excess_bits != None:
  321 + hid_data[hid_ind:hid_ind + self.excess_bits.size] = \
  322 + self.excess_bits
  323 + hid_ind += self.excess_bits.size
  324 + remaining_bits -= self.excess_bits.size
  325 +
  326 + curr_chunk = np.zeros(k, np.uint8)
  327 +
  328 + update_cnt = int(num_bits / (100.0 * k))
  329 +
  330 + while remaining_bits > 0:
  331 +
  332 + if update_cnt == 0 and not is_header:
  333 + self._set_progress(int(((float(num_bits) \
  334 + - remaining_bits) / num_bits) * 100))
  335 + update_cnt = int(num_bits / (100.0 * k))
  336 +
  337 + update_cnt -= 1
  338 +
  339 + steg_chunk = [0 for i in xrange(n)]
  340 + for i in xrange(n):
  341 + self.steg_ind += 1
  342 + while self.steg_data[dct_p[self.steg_ind]] == 0 or\
  343 + dct_p[self.steg_ind] % 64 == 0:
  344 + self.steg_ind += 1
  345 + steg_chunk[i] = self.steg_data[dct_p[self.steg_ind]]
  346 +
  347 + h = 0 # hash value
  348 + for i in xrange(n):
  349 + h ^= ((steg_chunk[i] & 1) * (i + 1))
  350 +
  351 + for i in xrange(k):
  352 + curr_chunk[k - i - 1] = h % 2
  353 + h /= 2
  354 +
  355 + l = min(k, remaining_bits)
  356 + for i in xrange(l):
  357 + hid_data[hid_ind] = curr_chunk[i]
  358 + hid_ind += 1
  359 +
  360 + # save excess bits (for later calls)
  361 + if k > remaining_bits:
  362 + self.excess_bits = curr_chunk[remaining_bits:]
  363 + else:
  364 + self.excess_bits = None
  365 +
  366 + remaining_bits -= k
  367 +
  368 + self.is_header = False
  369 + return hid_data
  370 +
  371 + def __str__(self):
  372 + return 'F5'
... ...
msteg/steganography/LSB.py
... ... @@ -10,203 +10,83 @@ bits a parameter named word_size. Thus --- in this context --- word means
10 10 import time
11 11 import numpy as np
12 12 import scipy as sp
13   -from stegotool.util.lookup import Lookup
14   -from stegotool.util.plugins import StegoBase
15   -from stegotool.util.plugins import describe_annotate_convert
16   -from stegotool.util.plugins import ident, ImagePath, FilePath, NewFilePath
17   -import Image
18   -
19   -
20   -class LSB(StegoBase):
21   - """ This module has 4 methods:
22   - <ul>
23   - <li><i>embed_image</i> and <i>extract_image</i> to embed/extract
24   - images.</li>
25   - <li><i>embed_raw_data</i> and <i>extract_raw_data</i> to embed/extract
26   - any data.</li>
27   - </ul>
28   - """
  13 +from StegBase import *
  14 +from common import *
  15 +
  16 +
  17 +class LSB(StegBase):
  18 + """ This module has two methods: <i>embed_raw_data</i> to embed data
  19 + with the F3 algorithm and <i>extract_raw_data</i> to extract data
  20 + which was embedded previously. """
  21 +
  22 + def __init__(self):
  23 + """
  24 + Constructor of the F3 class.
  25 + """
  26 + StegBase.__init__(self)
  27 +
  28 + def embed_raw_data(self, src_cover, src_hidden, tgt_stego):
  29 + """ This method embeds arbitrary data into a cover image.
  30 + The cover image must be a JPEG.
  31 +
  32 + src_cover - A valid pathname to an image file which serves as cover image
  33 + (the image which the secret image is embedded into).
  34 +
  35 + src_hidden - A valid pathname to an arbitrary file that is supposed to be
  36 + embedded into the cover image.
29 37  
30   - def __init__(self, ui, core):
  38 + tgt_stego - Target pathname of the resulting stego image. You should save to a
  39 + PNG or another lossless format, because many LSBs don't survive
  40 + lossy compression.
31 41 """
32   - Constructor of the LSB class.
  42 + self.t0 = time.time()
  43 + StegBase._post_embed_actions(self, src_cover, src_hidden, tgt_stego)
  44 +
  45 + def extract_raw_data(self, src_steg, tgt_hidden):
  46 + """ This method extracts secret data from a stego image. It is
  47 + (obviously) the inverse operation of embed_raw_data.
  48 +
  49 + src_stego - A valid pathname to an image file which serves as stego image.
  50 +
  51 + tgt_hidden - A pathname denoting where the extracted data should be saved to.
  52 + """
  53 + self.t0 = time.time()
  54 + StegBase._post_extract_actions(self, src_steg, tgt_hidden)
  55 +
  56 + def _raw_embed(self, cov_data, hid_data, status_begin=0):
33 57 """
34   - super(LSB, self).__init__(ui, core)
35   - self.lookup_tab = Lookup(True)
36   -
37   - @describe_annotate_convert((None, None, ident),
38   - ("cover image", ImagePath, str),
39   - ("hidden data", FilePath, str),
40   - ("stego image", NewFilePath, str),
41   - ("word size", [1, 2, 4, 8], int))
42   - def embed_raw_data(self, src_cover, src_hidden, tgt_stego, word_size):
43   - """ <p>This method embeds arbitrary data into a cover image.
44   - Note that the cover image is of course decoded
45   - before embedding, the secret data however is not.</p>
46   -
47   - <p>Parameters:
48   - <ol>
49   - <li><pre>src_cover</pre>
50   - A valid pathname to an image file which serves as cover image
51   - (the image which the secret image is embedded into).</li>
52   -
53   - <li><pre>src_hidden</pre>
54   - A valid pathname to an arbitrary file that is supposed to be
55   - embedded into the cover image.</li>
56   -
57   - <li><pre>tgt_stego</pre>
58   - Target pathname of the resulting stego image. You should save to
59   - a PNG or another lossless format, because many LSBs don't survive
60   - lossy compression.</li>
61   -
62   - <li><pre>word_size</pre>
63   - Must be an even divisor of 8, i.e. one of 1, 2, 4, 8. Specifies how
64   - many least significant bits of each byte in the cover image are used
65   - for embedding the secret data. The larger this number the more easily
66   - successful steganalysis can be carried out.</li>
67   -
68   - <li><pre>scatter</pre>
69   - If true, bytes used for embedding will be evenly distributed over the
70   - entire cover image. This(slightly) complicates steganalysis. If false,
71   - consecutive pixels are used for embedding, beginning with the first
72   - byte in the cover image, until the secret data is exhausted.</li>
73   - </ol>
74   - An exception is raised if the secret data is too
75   - large to be embedded into the cover image.</p>
  58 + cov_data - 4-D numpy.int32 array
  59 + hid_data - 1-D numpy.uint8 array
  60 + """
  61 + hid_data = bytes2bits(hid_data)
  62 + i = 0
  63 + cnt = -1
  64 + for x in np.nditer(cov_data, op_flags=['readwrite']):
  65 + cnt = cnt + 1
  66 + if x == 0 or x == 1 or cnt % 64 == 0: continue
  67 +
  68 + m = (hid_data[i] & 1)
  69 + x[...] = (x & 0xfffffffe) | m
  70 + i += 1
  71 + if i == hid_data.size: break
  72 +
  73 + return cov_data
  74 +
  75 + def _raw_extract(self, steg_data, num_bits):
76 76 """
77   - t0 = time.time()
78   - self.ui.set_progress(1)
79   - word_size = int(word_size)
80   - cov_img = self.core.media_manager.get_file(src_cover)
81   - if cov_img and isinstance(cov_img, Image.Image):
82   - cov_data = sp.misc.fromimage(cov_img)
83   - elif cov_img and hasattr(cov_img, "data"):
84   - raise Exception("cover image must be an image")
85   - else:
86   - cov_data = sp.misc.fromimage(Image.open(src_cover))
87   -
88   - orig_shape = cov_data.shape
89   - cov_data = cov_data.ravel()
90   -
91   - hidden_data_suffix = self.core.media_manager.get_file(src_hidden)
92   - if hidden_data_suffix and hasattr(hidden_data_suffix, 'data'):
93   - hidden_data_suffix = np.fromstring(hidden_data_suffix.data,
94   - np.uint8)
95   - elif hidden_data_suffix and hasattr(hidden_data_suffix, 'tmp_file'):
96   - hidden_data_suffix = np.fromfile(hidden_data_suffix.tmp_file,
97   - np.uint8)
98   - else:
99   - hidden_data_suffix = np.fromfile(src_hidden, np.uint8)
100   -
101   - hid_data = np.append(np.zeros(4, np.uint8), hidden_data_suffix)
102   -
103   - # write out the size of the hidden data
104   - size_hd = np.size(hid_data) - 4
105   -
106   - for i in xrange(4):
107   - hid_data[i] = size_hd % 256
108   - size_hd /= 256
109   -
110   - crypto = self.core.crypto_manager.get_instance()
111   - hid_data = crypto.encrypt(hid_data)
112   - self.ui.display_status('Encryption of data: %s' % crypto.info())
113   -
114   - if np.size(hid_data) * (8 / word_size) > np.size(cov_data):
115   - raise Exception("Cover image is too small to embed data. Try " +
116   - "increasing the word size or choosing a larger " +
117   - "cover image.")
118   -
119   - # converting hid_data to a sequence of words whose length is specified
120   - # by word_size
121   - hid_data = self.lookup_tab.to_word_sequence(hid_data, word_size)
122   - self.ui.set_progress(2)
123   - embedded = cov_data[:np.size(hid_data)]
124   - self.ui.set_progress(5)
125   - embedded = (embedded & ((0xff >> word_size)
126   - << word_size)) | hid_data
127   - self.ui.set_progress(95)
128   - cov_data[:np.size(hid_data)] = embedded
129   - self.ui.set_progress(98)
130   - cov_data = cov_data.reshape(orig_shape)
131   - cov_img = sp.misc.toimage(cov_data)
132   -
133   - if self.core.media_manager.is_media_key(tgt_stego):
134   - self.core.media_manager.put_media(tgt_stego, cov_img)
135   - else:
136   - cov_img.save(tgt_stego)
137   -
138   - size_embedded = np.size(hid_data) / (8 / word_size)
139   - self.ui.set_progress(99)
140   - self._display_stats("embedded", np.size(cov_data),
141   - size_embedded, time.time() - t0)
142   -
143   - @describe_annotate_convert((None, None, ident),
144   - ("stego image", ImagePath, str),
145   - ("hidden data", NewFilePath, str),
146   - ("word size", [1, 2, 4, 8], int))
147   - def extract_raw_data(self, src_steg, tgt_hidden, word_size):
148   - """ <p>This method extracts secret data from a stego image. It is
149   - (obviously) the inverse operation of embed_raw_data.</p>
150   -
151   - <p>Parameters:
152   - <ol>
153   - <li><pre>src_stego</pre>
154   - A valid pathname to an image file which serves as stego image.</li>
155   -
156   - <li><pre>tgt_hidden</pre>
157   - A pathname denoting where the extracted data should be saved to.</li>
158   -
159   - <li><pre>word_size</pre>
160   - Number of overwritten bits when the data was embedded.</li>
161   - </ol>
162   - An exception is raised if supposed secret data is too large to fit in
163   - the stego image.</p>
  77 + Just a small helper function to extract hidden data.
164 78 """
165   - t0 = time.time()
166   - self.ui.set_progress(1)
167   - word_size = int(word_size)
168   - steg_img = self.core.media_manager.get_file(src_steg)
169   -
170   - if steg_img and isinstance(steg_img, Image.Image):
171   - steg_data = sp.misc.fromimage(steg_img).ravel()
172   - elif steg_img and hasattr(steg_img, "data"):
173   - raise Exception("stego image must be an image, \
174   - not an arbitrary data file")
175   - else:
176   - steg_data = sp.misc.fromimage(Image.open(src_steg)).ravel()
177   -
178   - # recovering file size
179   - header_size = 4 * (8 / word_size)
180   - size_data = steg_data[:header_size] & (0xff >> (8 - word_size))
181   -
182   - size_data = self.lookup_tab.to_byte_sequence(size_data, word_size)
183   - crypto = self.core.crypto_manager.get_instance()
184   - size_data = crypto.decrypt(size_data)
185   - self.ui.display_status('Decryption of size data: %s' % crypto.info())
186   -
187   - size_hd = 0
188   - for i in xrange(4):
189   - size_hd += size_data[i] * 256 ** i
190   -
191   - raw_size = size_hd * (8 / word_size)
192   -
193   - if raw_size > np.size(steg_data):
194   - raise Exception("Supposed secret data too large for stego image.")
195   - self.ui.set_progress(5)
196   - hid_data = steg_data[header_size:raw_size + header_size] \
197   - & (0xff >> (8 - word_size))
198   - self.ui.set_progress(90)
199   - hid_data = self.lookup_tab.to_byte_sequence(hid_data, word_size)
200   - self.ui.set_progress(95)
201   - final_data = crypto.decrypt(hid_data)
202   - self.ui.display_status('Decryption of data: %s' % crypto.info())
203   -
204   - self._extract_to_image_pool(tgt_hidden, final_data)
205   -
206   - self.ui.set_progress(99)
207   - self._display_stats("extracted", np.size(steg_data),
208   - np.size(hid_data) + np.size(size_data),
209   - time.time() - t0)
  79 + hid_data = np.zeros(num_bits, np.uint8)
  80 + j = 0
  81 + cnt = -1
  82 + for x in np.nditer(steg_data):
  83 + cnt = cnt + 1
  84 + if x == 0 or x == 1 or cnt % 64 == 0: continue
  85 + if j >= num_bits: break
  86 + hid_data[j] = x & 1
  87 + j = j + 1
  88 +
  89 + return hid_data
210 90  
211 91 def __str__(self):
212 92 return 'LSB'
... ...
test_jpeg.py
... ... @@ -193,6 +193,7 @@ if __name__ == &#39;__main__&#39;:
193 193 c2 = imb.getCoefBlocks()
194 194  
195 195 print c1[0],c2[0]
  196 +
196 197 pass
197 198  
198 199  
... ...