While the GPGME Python bindings provide interface to most of the
functionality provided by GnuPG, so I could generate keys and perform
encryption and decryption using them, I found that it wasn't possible to
list, which public keys can decrypt a PGP encrypted file. Of course, it's
always a possibility to invoke the gpg
binary, but I wanted to avoid
spawning processes if possible.
As Heikki Toivonen mentioned in a Stack Overflow thread, the
M2Crypto library had a PGP module, and based on a demo code, it
seemed to be able to parse OpenPGP files into meaningful structures, including
pke_packet
that contains an attribute called keyid
. I installed the module
from the Debian package python-m2crypto
, tried calling the PGP parser
functionality, and found that
- the
keyid
attribute is called_keyid
now, and - after returning the
pke_packet
instances, it raises anXXXError
in case of OpenPGP output generated by GnuPG 1.4.12.
It's also important to note that the M2Crypto keyid is an 8 bytes long raw
byte string, while GPGME uses 16 characters long uppercase hex strings for the
same purpose. I chose to convert the former to the latter format, resulting in
a set
of hexadecimal key IDs. Later, I could check, which keys available in
the current keyring are able to decrypt the file. The get_acl
function thus
returns a dict
mapping e-mail addresses to a boolean value that indicates
the key's ability to decrypt the file specified in the filename
parameter.
from M2Crypto import PGP
from contextlib import closing
from binascii import hexlify
import gpgme
def get_acl(filename):
with file(filename, 'rb') as stream:
with closing(PGP.packet_stream(stream)) as ps:
own_pk = set(packet_stream2hexkeys(ps))
return dict(
(k.uids[0].email, any(s.keyid in own_pk for s in k.subkeys))
for k in gpgme.Context().keylist())
def packet_stream2hexkeys(ps):
try:
while True:
pkt = ps.read()
if pkt is None:
break
elif pkt and isinstance(pkt, PGP.pke_packet):
yield hexlify(pkt._keyid).upper()
except:
pass