1

I want to gpg --sign the pixel data in a PNG image. Later, I want to be able to gpg --verify the signature on the pixel data as-found to know the image's author and that it hasn't changed since signing.

Motivation

I am developing a system which allows the integrity of PNG images to be verified and attributed (cf. non-repudiation) at a later date. I am working in Python. Ideally, I want the additional information to not hinder any user from reading the image as usual.

What is important in an image? In my application, only the pixel data is important. I need to get this data into a form which gpg --sign accepts, which amounts to serializing a Python object. The ability to verify a signature in the future of an image which has not been tampered with relies upon the serialization (pickle.dumps, cPickle.dumps) producing the same output say 5 years from now.

Why not just sign the file with gpg --sign? Sticking the signature of the pixel data back in the same image's metadata keeps the signature 1. attached and 2. valid. Anywhere the image goes, so goes the signature (apart from any overzealous software which might remove it).

The example below is meant to be run in ipython.

Sign a PNG image.

# The PNG image to sign.
f = 'image.png'

# Read pixel data from a PNG image.
from pillow import Image
serialized = Image.open(f).tobytes(encoder_name='raw')

open('serialized.txt').write(pixel_data)

# Sign the data with gpg using the default private key.
!gpg --sign --detach --armor --output sig.asc serialized.txt
# <enter gpg passphrase as required>

# Write the signature to the PNG image's metadata using Image Magick.
import subprocess
subprocess.call(['mogrify', '-set', 'gpgsig', open('sig.asc').read()])

# Verify it got added using Image Magick.
!identify -verbose image.png

Much, much later, attempt to verify the signature

# Many, many moons go by, and you find a PNG image in the wild.
# It looks familiar, but its provenance and integrity are unknown.
# But lo! it includes a gpg signature in its metadata.
# You want to verify the signature (which may have been signed
# by yourself or anybody else whose gpg public key you have
# access to) to ensure the image is unchanged.

# Read the pixel data, same as before.
from pillow import Image
serialized = Image.open(f).tobytes(encoder_name='raw')

open('serialized.txt').write(serialized)

# Get the included signature.  This may be done
# programmatically, but for the sake of simplicity,
# just use Image Magick.
!identify -verbose image.png

# Save the signature to sig.txt.

# Attempt to verify the detached signature
!gpg --verify sig.txt serialized.txt
Adam Fuller
  • 163
  • 1
  • 9

1 Answers1

1

I would just sign it in memory using pycrypto to hash and sign it, I think. Serialization is an extra step that isn't necessary.

Edit: Here is a previous Q/A on this:

Signing and verifying data using pycrypto (RSA)

Edit two after reading comment below:

I believe that the PIL tostring() function just dumps the pixel data into a string, e.g. it is pre-serialized. So if you really want to use an external program, perhaps you can simply output this string to a file.

Community
  • 1
  • 1
Patrick Maupin
  • 7,331
  • 2
  • 21
  • 41
  • 1
    Hmm, I hadn't considered pycrypto and haven't actually used it before. I'm not sure it provides the same "nice" UI that running command-line gpg will do e.g. automatically picking your default private key and getting the key's passphrase from you interactively. Also important for my application, pycrypto.hash.MD5 looks to only accept string-like input, so my pixel data would still need to be serialized in some way. – Adam Fuller Jul 31 '15 at 03:23
  • I updated the answer to describe how you can get the serialized data out of PIL. – Patrick Maupin Jul 31 '15 at 04:12
  • This looks promising. I need to read about PIL.Image.tostring and its "raw" encoder to confirm its just looking at the pixel data. In [74]: len(cPickle.dumps(list(Image.open(f).getdata()))) Out[74]: 73578551 In [75]: len(Image.open(f).tostring()) Out[75]: 9726760 – Adam Fuller Jul 31 '15 at 04:44
  • import pillow as PIL; PIL.Image.open(f).tobytes does return pixel data only. I wasn't able to find the documentation for pillow's "raw" encoder... On the plus side, it is much smaller and faster than cPickle.dumps (thanks for eliminating pickle!). For my application, I suppose the important question is now "how stable is the output of pillow.Image.tobytes?", remembering that I will rely on identical pixel data in the future to sign/hash identically for signature verification. – Adam Fuller Aug 02 '15 at 21:42
  • Yeah, that I'm not sure about. But I think with your original strategy you'd still essentially have that problem (as well as the one of worrying about the stability of pickle). In fact, if the "raw" bytes come straight from the PNG file, they might be more stable than the decoded pixels you would get the other way... – Patrick Maupin Aug 03 '15 at 16:33