Course Info‎ > ‎

Steganalysis Code

Here is code for the steganalysis lab.



import os,os.path,zlib

import struct
from pylab import *
from PIL import Image

### LSB steganography
###
### We're only embedding in grayscale images here.

def iread(file):
    """Read an image file as an integer array, then pick
    the G channel if it is a color image."""
    image = array(Image.open(file))
    if image.ndim==3: image = image[:,:,1]
    return image

def bitunpack(data):
    """Unpack an 8bit byte array into an array into an
    array of binary values."""
    data = array(bytearray(data),'B')
    n = len(data)
    bits = zeros(8*n,'B')
    index = arange(n)
    for i in range(8):
        bits[8*index+i] = (data&(1<<i)!=0)
    return bits

def bitpack(bits):
    """Pack an array of binary values into an 8bit
    byte array."""
    n = int(len(bits)/8)
    data = zeros(n,'B')
    index = arange(n)
    for i in range(8):
        data += (bits[8*index+i]!=0)*(1<<i)
    return bytearray(data)

def lsb_encode(cover,data):
    """Encode the given data into the LSB of the cover image.
    Also encodes the data length."""
    assert cover.ndim==2
    data = struct.pack("I",len(data))+bytearray(data)
    result = cover.copy()
    bits = bitunpack(data)
    assert len(bits)>=len(data),"too many bits for the given image size"
    seq = result.ravel()[:len(bits)]
    assert seq.size>=bits.size
    print seq.shape,bits.shape
    seq[:] = ((seq&0xfffffffe) | (bits!=0))
    return result

def lsb_decode(image):
    """Decode the LSB data from the image into a byte array."""
    assert image.ndim==2
    bits = (array(image,'B').ravel()&1)
    data = bitpack(bits)
    l = struct.unpack_from("I",str(data[:4]))[0]
    return data[4:4+l]

### RS steganalysis
###
### This is a simple implementation; among other things,
### it only allows for 1D bit patterns in the analysis.

def rs_discriminant(image,g=3):
    """Compute the RS discriminant function.
    g is the group size."""
    image = array(image.ravel(),'f')
    deltas = abs(image[:-1]-image[1:])
    n = len(deltas)
    ng = (n+g-1)/g
    global groups
    groups = array(arange(n)/g,'i')
    totals = zeros(ng)
    totals[groups] += deltas
    return totals


def rs_vals(image_,ntrials=10,mask=array([0,1,1,0])):
    """Compute the R and S values of RS steganalysis for
    the given mask."""
    assert image_.ndim==2
    image = array(image_.ravel(),'i')
    # imshow(image.reshape(image_.shape))
    bits = array(tile(mask,1+image.size/mask.size)[:image.size],'i')
    before = rs_discriminant(image)
    steg = (image^bits)
    after = rs_discriminant(steg)
    steg1 = ((image+1)^bits)-1
    after1 = rs_discriminant(steg1)
    R = sum(before<after)*1.0/before.size
    R1 = sum(before<after1)*1.0/before.size
    S = sum(before>after)*1.0/before.size
    S1 = sum(before>after1)*1.0/before.size
    return (R,R1,S,S1)

def rs_plot(image):
    """Plot the RS values for a given image, embedding different fractions
    of random bits in the image."""
    assert image.ndim==2
    clf()
    data = []
    ps = linspace(0.0,1.0,41)
    for p in ps:
        bits = 1*(uniform(size=image.shape)<p)
        steg = image^bits
        data.append(rs_vals(steg))
    data = array(data)
    plot(ps,data[:,0],'r',ps,data[:,1],'b',ps,data[:,2],'g',ps,data[:,3],'y')

masks = [array(uniform(size=11)>0.5,'i') for i in range(10)]

def rs_plot1(image,masks=masks):
    """Plot the RS values for a given image, embedding different fractions
    of random bits in the image.  Averages the values using multiple
    masks to achieve a smoother plot."""
    assert image.ndim==2
    clf()
    data = []
    ps = linspace(0.0,1.0,41)
    for p in ps:
        v = zeros(4)
        for mask in masks:
            bits = 1*(uniform(size=image.shape)<p)
            steg = image^bits
            v += array(rs_vals(steg,mask=mask))
        data.append(v/len(masks))
    data = array(data)
    plot(ps,data[:,0],'r',ps,data[:,1],'b',ps,data[:,2],'g',ps,data[:,3],'y')

### compression-based steganalysis
   
def zlib_size(image):
    """Return the size of the zlib compressed LSB bitplane."""
    return len(zlib.compress(str(bytearray((image&1).ravel()))))
   
def png_size(image):
    """Return the size of the PNG compressed image."""
    Image.fromarray(image).save("/tmp/_test.png")
    return os.path.getsize("/tmp/_test.png")

def lsb_fft_show(image):
    """Display the log spectrum of the LSB of (part of the image);
    useful for showing differences in spatial frequencies in LSB
    patterns of images containing hidden data."""
    assert image.ndim==2
    q = fft(1.0*(image[:256,:256]&1))
    ion()
    imshow(log(abs(q)+1))
    draw()

Comments