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
keyidattribute is called_keyidnow, and - after returning the
pke_packetinstances, it raises anXXXErrorin 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