Solving problems – encrypting a message

For proper encryption, the PyCrypto package can be used, which can be downloaded from https://www.dlitz.net/software/pycrypto/. As with Pillow, this is a hefty download.

As we saw in Chapter 1, Our Espionage Toolkit, a poor choice of key will render any encryption scheme essentially worthless. If we encrypt a file using a single-word key that's available in a readily available corpus of words, we haven't really made our data very secure at all. A brute-force attack will break the encryption.

We can combine steganography with the creation of a ZipFile archive to embed a message in an image in a ZIP file. As a ZIP file can have a comment string, we can include an HMAC signature as the comment for the ZIP archive.

Ideally, we'd use the ZIP encryption. However, the Python ZipFile library doesn't create encrypted ZIP files. It only reads encrypted files.

We're going to define a function that looks like this:

def package( text, image_source, key_hmac, filename ):

We'll provide the text of our message, the image source, the key we'll use to create our HMAC signature, and an output filename. The output from this will be a ZIP file that contains the image and the signature.

The outline of our package() function looks like this:

    image= Image.open( image_source )
    steg_embed( image, text )
    image.save( "/tmp/package.tiff", format="TIFF" )
    with open("/tmp/package.tiff","rb") as saved:
        digest= hmac.new( key_hmac.encode("ASCII"), saved.read() )
    with ZipFile( filename, "w" ) as archive:
        archive.write( "/tmp/package.tiff", "image.tiff" )
        archive.comment= digest.hexdigest().encode("ASCII")
    os.remove( "/tmp/package.tiff" )

We've opened the source image and used an steg_embed() function to put our secret message into the image. We've saved the updated image to a temporary file.

We computed the HMAC digest of our image file before doing anything else with it. We saved the digest in the digest variable.

Now that everything is prepared, we can create a new archive file. We can write the image into one member of the archive. When we are setting the comment attribute of the archive, this will make sure that the comment text is written when the archive is closed.

Note that we had to convert the key to ASCII bytes to create a digest. The HMAC algorithm is defined for bytes, not Unicode characters. Similarly, the resulting hexdigest() string had to be converted to ASCII bytes before it could be placed in the archive. ZIP archives only work in bytes and can't directly support Unicode characters.

Finally, we removed the temporary file that had the tweaked image in it. There's no reason to leave potentially incriminating files laying around.

For this to work, we need to complete the function, steg_embed(), that implements our steganographic encoding. See the Some approaches to steganography section for details on how this needs to work.

Unpacking a message

We'll also need the inverse function that can decode a message in a ZIP archive. This function would have a definition like this:

def unpackage( filename, key_hmac ):

It requires a ZIP filename and a key to validate the signature. This can return two things: the embedded message and the image into which the message was encoded.

The outline of our unpackage() function looks like this:

    try:
        os.remove( "/tmp/image.tiff" )
    except FileNotFoundError:
        pass
    with ZipFile( filename, "r" ) as archive:
        with archive.open( "image.tiff", "r" ) as member:
            keyed= hmac.new( key_hmac.encode("ASCII"), member.read() )
        assert archive.comment == keyed.hexdigest().encode("ASCII"), "Invalid HMAC"
        archive.extract( "image.tiff", "/tmp" )
    image= Image.open( "/tmp/image.tiff" )
    text= steg_extract( image )
    os.remove( "/tmp/image.tiff" )
    return text, image

We're going to remove any temporary file that might exist. If the file doesn't exist already, that's a good thing, but it will raise a FileNotFoundError exception. We need to trap and silence that exception.

Our first step is to open the ZIP file and then open the image.tiff member within the ZIP file. We compute the HMAC digest of this member. Then, we assert that the archive comment matches the hex digest of the selected member. If the condition in the assert statement is false and the HMAC keys don't match, then this will raise an exception and the script will stop running. This will also mean that our message was compromised. If the condition in the assert statement is true, it executes silently.

If the assertion is true, we can extract the image file to a spot in the /tmp directory. From here, we can open the file and use the steg_extract() function to recover the message hidden in the image. Windows agents can use the os module to locate a temporary directory. The value of os.environ['TEMP'] will name a suitable temporary directory.

Once we've got the message, we can remove the temporary file.

For this to work, we need to complete the function, steg_extract(), that implements our steganographic decoding. See the Some approaches to steganography section for details on how this needs to work.