summaryrefslogtreecommitdiff
path: root/gnupg/gnupg.py
diff options
context:
space:
mode:
Diffstat (limited to 'gnupg/gnupg.py')
-rw-r--r--gnupg/gnupg.py1067
1 files changed, 1067 insertions, 0 deletions
diff --git a/gnupg/gnupg.py b/gnupg/gnupg.py
new file mode 100644
index 0000000..9aa8232
--- /dev/null
+++ b/gnupg/gnupg.py
@@ -0,0 +1,1067 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of python-gnupg, a Python interface to GnuPG.
+# Copyright © 2013 Isis Lovecruft, <isis@leap.se> 0xA3ADB67A2CDB8B35
+# © 2013 Andrej B.
+# © 2013 LEAP Encryption Access Project
+# © 2008-2012 Vinay Sajip
+# © 2005 Steve Traugott
+# © 2004 A.M. Kuchling
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the included LICENSE file for details.
+
+"""gnupg.py
+===========
+A Python interface to GnuPG.
+
+.. moduleauthor:: Isis Lovecruft <isis@patternsinthevoid.net>
+ see also :attr:`gnupg.__authors__`
+.. license:: see :attr:`gnupg.__license__`
+.. info:: https://github.com/isislovecruft/python-gnupg
+"""
+
+from __future__ import absolute_import
+from codecs import open as open
+
+import encodings
+import functools
+import os
+import re
+import textwrap
+
+try:
+ from io import StringIO
+except ImportError:
+ from cStringIO import StringIO
+
+#: see :pep:`328` http://docs.python.org/2.5/whatsnew/pep-328.html
+from . import _parsers
+from . import _util
+from . import _trust
+from ._meta import GPGBase
+from ._parsers import _fix_unsafe
+from ._util import _is_list_or_tuple
+from ._util import _is_stream
+from ._util import _make_binary_stream
+from ._util import log
+
+
+class GPG(GPGBase):
+ """Python interface for handling interactions with GnuPG, including keyfile
+ generation, keyring maintainance, import and export, encryption and
+ decryption, sending to and recieving from keyservers, and signing and
+ verification.
+ """
+
+ #: The number of simultaneous keyids we should list operations like
+ #: '--list-sigs' to:
+ _batch_limit = 25
+
+ def __init__(self, binary=None, homedir=None, verbose=False,
+ use_agent=False, keyring=None, secring=None,
+ options=None):
+ """Initialize a GnuPG process wrapper.
+
+ :param str binary: Name for GnuPG binary executable. If the absolute
+ path is not given, the environment variable
+ ``$PATH`` is searched for the executable and
+ checked that the real uid/gid of the user has
+ sufficient permissions.
+
+ :param str homedir: Full pathname to directory containing the public
+ and private keyrings. Default is whatever GnuPG
+ defaults to.
+
+ :type verbose: :obj:`str` or :obj:`int` or :obj:`bool`
+ :param verbose: String or numeric value to pass to GnuPG's
+ ``--debug-level`` option. See the GnuPG man page for
+ the list of valid options. If False, debug output is
+ not generated by the GnuPG binary. If True, defaults
+ to ``--debug-level basic.``
+
+ :param str keyring: Name of keyring file containing public key data.
+ If unspecified, defaults to :file:`pubring.gpg` in
+ the **homedir** directory.
+
+ :param str secring: Name of alternative secret keyring file to use. If
+ left unspecified, this will default to using
+ :file:`secring.gpg` in the **homedir** directory,
+ and create that file if it does not exist.
+
+ :param list options: A list of additional options to pass to the GnuPG
+ binary.
+
+ :raises: A :exc:`~exceptions.RuntimeError` with explanation message
+ if there is a problem invoking GnuPG.
+
+ Example:
+
+ >>> import gnupg
+ GnuPG logging disabled...
+ >>> gpg = gnupg.GPG(homedir='doctests')
+ >>> gpg.keyring
+ './doctests/pubring.gpg'
+ >>> gpg.secring
+ './doctests/secring.gpg'
+ >>> gpg.use_agent
+ False
+ >>> gpg.binary
+ '/usr/bin/gpg'
+ """
+
+ super(GPG, self).__init__(
+ binary=binary,
+ home=homedir,
+ keyring=keyring,
+ secring=secring,
+ options=options,
+ verbose=verbose,
+ use_agent=use_agent,)
+
+ log.info(textwrap.dedent("""
+ Initialised settings:
+ binary: %s
+ homedir: %s
+ keyring: %s
+ secring: %s
+ default_preference_list: %s
+ keyserver: %s
+ options: %s
+ verbose: %s
+ use_agent: %s
+ """ % (self.binary, self.homedir, self.keyring, self.secring,
+ self.default_preference_list, self.keyserver, self.options,
+ str(self.verbose), str(self.use_agent))))
+
+ self._batch_dir = os.path.join(self.homedir, 'batch-files')
+ self._key_dir = os.path.join(self.homedir, 'generated-keys')
+
+ #: The keyring used in the most recently created batch file
+ self.temp_keyring = None
+ #: The secring used in the most recently created batch file
+ self.temp_secring = None
+ #: The version string of our GnuPG binary
+ self.binary_version = str()
+
+ ## check that everything runs alright, and grab the gpg binary's
+ ## version number while we're at it:
+ proc = self._open_subprocess(["--list-config", "--with-colons"])
+ result = self._result_map['list'](self)
+ self._read_data(proc.stdout, result)
+ if proc.returncode:
+ raise RuntimeError("Error invoking gpg: %s" % result.data)
+
+ version_line = str(result.data).partition(':version:')[2]
+ self.binary_version = version_line.split('\n')[0]
+ log.debug("Using GnuPG version %s" % self.binary_version)
+
+ if _util._is_gpg2:
+ # Make GnuPG>=2.0.0-only methods public:
+ self.fix_trustdb = self._fix_trustdb
+ self.import_ownertrust = self._import_ownertrust
+ self.export_ownertrust = self._export_ownertrust
+
+ # Make sure that the trustdb exists, or else GnuPG will exit with
+ # a fatal error (at least it does with GnuPG>=2.0.0):
+ self._create_trustdb()
+
+ @functools.wraps(_trust._create_trustdb)
+ def _create_trustdb(self):
+ if self.is_gpg2():
+ _trust._create_trustdb(self)
+ else:
+ log.info("Creating the trustdb is only available with GnuPG>=2.x")
+
+ @functools.wraps(_trust.fix_trustdb)
+ def _fix_trustdb(self, trustdb=None):
+ if self.is_gpg2():
+ _trust.fix_trustdb(self)
+ else:
+ log.info("Fixing the trustdb is only available with GnuPG>=2.x")
+
+ @functools.wraps(_trust.import_ownertrust)
+ def _import_ownertrust(self, trustdb=None):
+ if self.is_gpg2():
+ _trust.import_ownertrust(self)
+ else:
+ log.info("Importing ownertrust is only available with GnuPG>=2.x")
+
+ @functools.wraps(_trust.export_ownertrust)
+ def _export_ownertrust(self, trustdb=None):
+ if self.is_gpg2():
+ _trust.export_ownertrust(self)
+ else:
+ log.info("Exporting ownertrust is only available with GnuPG>=2.x")
+
+ def is_gpg1(self):
+ """Returns true if using GnuPG <= 1.x."""
+ return _util._is_gpg1(self.binary_version)
+
+ def is_gpg2(self):
+ """Returns true if using GnuPG >= 2.x."""
+ return _util._is_gpg2(self.binary_version)
+
+ def sign(self, data, **kwargs):
+ """Create a signature for a message string or file.
+
+ Note that this method is not for signing other keys. (In GnuPG's
+ terms, what we all usually call 'keysigning' is actually termed
+ 'certification'...) Even though they are cryptographically the same
+ operation, GnuPG differentiates between them, presumedly because these
+ operations are also the same as the decryption operation. If the
+ ``key_usage``s ``C (certification)``, ``S (sign)``, and ``E
+ (encrypt)``, were all the same key, the key would "wear down" through
+ frequent signing usage -- since signing data is usually done often --
+ meaning that the secret portion of the keypair, also used for
+ decryption in this scenario, would have a statistically higher
+ probability of an adversary obtaining an oracle for it (or for a
+ portion of the rounds in the cipher algorithm, depending on the family
+ of cryptanalytic attack used).
+
+ In simpler terms: this function isn't for signing your friends' keys,
+ it's for something like signing an email.
+
+ :type data: :obj:`str` or :obj:`file`
+ :param data: A string or file stream to sign.
+ :param str default_key: The key to sign with.
+ :param str passphrase: The passphrase to pipe to stdin.
+ :param bool clearsign: If True, create a cleartext signature.
+ :param bool detach: If True, create a detached signature.
+ :param bool binary: If True, do not ascii armour the output.
+ :param str digest_algo: The hash digest to use. Again, to see which
+ hashes your GnuPG is capable of using, do:
+ :command:`$ gpg --with-colons --list-config digestname`.
+ The default, if unspecified, is ``'SHA512'``.
+ """
+ if 'default_key' in kwargs:
+ log.info("Signing message '%r' with keyid: %s"
+ % (data, kwargs['default_key']))
+ else:
+ log.warn("No 'default_key' given! Using first key on secring.")
+
+ if hasattr(data, 'read'):
+ result = self._sign_file(data, **kwargs)
+ elif not _is_stream(data):
+ stream = _make_binary_stream(data, self._encoding)
+ result = self._sign_file(stream, **kwargs)
+ stream.close()
+ else:
+ log.warn("Unable to sign message '%s' with type %s"
+ % (data, type(data)))
+ result = None
+ return result
+
+ def verify(self, data):
+ """Verify the signature on the contents of the string ``data``.
+
+ >>> gpg = GPG(homedir="doctests")
+ >>> input = gpg.gen_key_input(Passphrase='foo')
+ >>> key = gpg.gen_key(input)
+ >>> assert key
+ >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='bar')
+ >>> assert not sig
+ >>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='foo')
+ >>> assert sig
+ >>> verify = gpg.verify(sig.data)
+ >>> assert verify
+
+ """
+ f = _make_binary_stream(data, self._encoding)
+ result = self.verify_file(f)
+ f.close()
+ return result
+
+ def verify_file(self, file, sig_file=None):
+ """Verify the signature on the contents of a file or file-like
+ object. Can handle embedded signatures as well as detached
+ signatures. If using detached signatures, the file containing the
+ detached signature should be specified as the ``sig_file``.
+
+ :param file file: A file descriptor object. Its type will be checked
+ with :func:`_util._is_file`.
+
+ :param str sig_file: A file containing the GPG signature data for
+ ``file``. If given, ``file`` is verified via this detached
+ signature.
+ """
+
+ fn = None
+ result = self._result_map['verify'](self)
+
+ if sig_file is None:
+ log.debug("verify_file(): Handling embedded signature")
+ args = ["--verify"]
+ proc = self._open_subprocess(args)
+ writer = _util._threaded_copy_data(file, proc.stdin)
+ self._collect_output(proc, result, writer, stdin=proc.stdin)
+ else:
+ if not _util._is_file(sig_file):
+ log.debug("verify_file(): '%r' is not a file" % sig_file)
+ return result
+ log.debug('verify_file(): Handling detached verification')
+ sig_fh = None
+ data_fh = None
+ try:
+ sig_fh = open(sig_file, 'rb')
+ data_fh = open(file, 'rb')
+ args = ["--verify %s -" % sig_fh.name]
+ proc = self._open_subprocess(args)
+ writer = _util._threaded_copy_data(data_fh, proc.stdin)
+ self._collect_output(proc, result, writer, stdin=proc.stdin)
+ finally:
+ if sig_fh and not sig_fh.closed:
+ sig_fh.close()
+ if data_fh and not data_fh.closed:
+ data_fh.close()
+ return result
+
+ def import_keys(self, key_data):
+ """
+ Import the key_data into our keyring.
+
+ >>> import shutil
+ >>> shutil.rmtree("doctests")
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> inpt = gpg.gen_key_input()
+ >>> key1 = gpg.gen_key(inpt)
+ >>> print1 = str(key1.fingerprint)
+ >>> pubkey1 = gpg.export_keys(print1)
+ >>> seckey1 = gpg.export_keys(print1,secret=True)
+ >>> key2 = gpg.gen_key(inpt)
+ >>> print2 = key2.fingerprint
+ >>> seckeys = gpg.list_keys(secret=True)
+ >>> pubkeys = gpg.list_keys()
+ >>> assert print1 in seckeys.fingerprints
+ >>> assert print1 in pubkeys.fingerprints
+ >>> str(gpg.delete_keys(print1))
+ 'Must delete secret key first'
+ >>> str(gpg.delete_keys(print1,secret=True))
+ 'ok'
+ >>> str(gpg.delete_keys(print1))
+ 'ok'
+ >>> pubkeys = gpg.list_keys()
+ >>> assert not print1 in pubkeys.fingerprints
+ >>> result = gpg.import_keys(pubkey1)
+ >>> pubkeys = gpg.list_keys()
+ >>> seckeys = gpg.list_keys(secret=True)
+ >>> assert not print1 in seckeys.fingerprints
+ >>> assert print1 in pubkeys.fingerprints
+ >>> result = gpg.import_keys(seckey1)
+ >>> assert result
+ >>> seckeys = gpg.list_keys(secret=True)
+ >>> assert print1 in seckeys.fingerprints
+ """
+ ## xxx need way to validate that key_data is actually a valid GPG key
+ ## it might be possible to use --list-packets and parse the output
+
+ result = self._result_map['import'](self)
+ log.info('Importing: %r', key_data[:256])
+ data = _make_binary_stream(key_data, self._encoding)
+ self._handle_io(['--import'], data, result, binary=True)
+ data.close()
+ return result
+
+ def recv_keys(self, *keyids, **kwargs):
+ """Import keys from a keyserver.
+
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key = gpg.recv_keys('hkp://pgp.mit.edu', '3FF0DB166A7476EA')
+ >>> assert key
+
+ :param str keyids: Each ``keyids`` argument should be a string
+ containing a keyid to request.
+ :param str keyserver: The keyserver to request the ``keyids`` from;
+ defaults to `gnupg.GPG.keyserver`.
+ """
+ if keyids:
+ keys = ' '.join([key for key in keyids])
+ return self._recv_keys(keys, **kwargs)
+ else:
+ log.error("No keyids requested for --recv-keys!")
+
+ def delete_keys(self, fingerprints, secret=False, subkeys=False):
+ """Delete a key, or list of keys, from the current keyring.
+
+ The keys must be referred to by their full fingerprints for GnuPG to
+ delete them. If ``secret=True``, the corresponding secret keyring will
+ be deleted from :obj:`.secring`.
+
+ :type fingerprints: :obj:`str` or :obj:`list` or :obj:`tuple`
+ :param fingerprints: A string, or a list/tuple of strings,
+ representing the fingerprint(s) for the key(s)
+ to delete.
+
+ :param bool secret: If True, delete the corresponding secret key(s)
+ also. (default: False)
+
+ :param bool subkeys: If True, delete the secret subkey first, then the
+ public key. (default: False) Same as:
+ :command:`$gpg --delete-secret-and-public-key 0x12345678`.
+ """
+ which = 'keys'
+ if secret:
+ which = 'secret-keys'
+ if subkeys:
+ which = 'secret-and-public-keys'
+
+ if _is_list_or_tuple(fingerprints):
+ fingerprints = ' '.join(fingerprints)
+
+ args = ['--batch']
+ args.append("--delete-{0} {1}".format(which, fingerprints))
+
+ result = self._result_map['delete'](self)
+ p = self._open_subprocess(args)
+ self._collect_output(p, result, stdin=p.stdin)
+ return result
+
+ def export_keys(self, keyids, secret=False, subkeys=False):
+ """Export the indicated ``keyids``.
+
+ :param str keyids: A keyid or fingerprint in any format that GnuPG will
+ accept.
+ :param bool secret: If True, export only the secret key.
+ :param bool subkeys: If True, export the secret subkeys.
+ """
+ which = ''
+ if subkeys:
+ which = '-secret-subkeys'
+ elif secret:
+ which = '-secret-keys'
+
+ if _is_list_or_tuple(keyids):
+ keyids = ' '.join(['%s' % k for k in keyids])
+
+ args = ["--armor"]
+ args.append("--export{0} {1}".format(which, keyids))
+
+ p = self._open_subprocess(args)
+ ## gpg --export produces no status-fd output; stdout will be empty in
+ ## case of failure
+ #stdout, stderr = p.communicate()
+ result = self._result_map['delete'](self) # any result will do
+ self._collect_output(p, result, stdin=p.stdin)
+ log.debug('Exported:%s%r' % (os.linesep, result.data))
+ return result.data.decode(self._encoding, self._decode_errors)
+
+ def list_keys(self, secret=False):
+ """List the keys currently in the keyring.
+
+ The GnuPG option '--show-photos', according to the GnuPG manual, "does
+ not work with --with-colons", but since we can't rely on all versions
+ of GnuPG to explicitly handle this correctly, we should probably
+ include it in the args.
+
+ >>> import shutil
+ >>> shutil.rmtree("doctests")
+ >>> gpg = GPG(homedir="doctests")
+ >>> input = gpg.gen_key_input()
+ >>> result = gpg.gen_key(input)
+ >>> print1 = result.fingerprint
+ >>> result = gpg.gen_key(input)
+ >>> print2 = result.fingerprint
+ >>> pubkeys = gpg.list_keys()
+ >>> assert print1 in pubkeys.fingerprints
+ >>> assert print2 in pubkeys.fingerprints
+ """
+
+ which = 'public-keys'
+ if secret:
+ which = 'secret-keys'
+ args = "--list-%s --fixed-list-mode --fingerprint " % (which,)
+ args += "--with-colons --list-options no-show-photos"
+ args = [args]
+ p = self._open_subprocess(args)
+
+ # there might be some status thingumy here I should handle... (amk)
+ # ...nope, unless you care about expired sigs or keys (stevegt)
+
+ # Get the response information
+ result = self._result_map['list'](self)
+ self._collect_output(p, result, stdin=p.stdin)
+ lines = result.data.decode(self._encoding,
+ self._decode_errors).splitlines()
+ valid_keywords = 'pub uid sec fpr sub'.split()
+ for line in lines:
+ if self.verbose:
+ print(line)
+ log.debug("%r", line.rstrip())
+ if not line:
+ break
+ L = line.strip().split(':')
+ if not L:
+ continue
+ keyword = L[0]
+ if keyword in valid_keywords:
+ getattr(result, keyword)(L)
+ return result
+
+ def list_packets(self, raw_data):
+ """List the packet contents of a file."""
+ args = ["--list-packets"]
+ result = self._result_map['packets'](self)
+ self._handle_io(args, _make_binary_stream(raw_data, self._encoding),
+ result)
+ return result
+
+ def list_sigs(self, *keyids):
+ """Get the signatures for each of the ``keyids``.
+
+ >>> import gnupg
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_input = gpg.gen_key_input()
+ >>> key = gpg.gen_key(key_input)
+ >>> assert key.fingerprint
+
+ :rtype: dict
+ :returns: A dictionary whose keys are the original keyid parameters,
+ and whose values are lists of signatures.
+ """
+ if len(keyids) > self._batch_limit:
+ raise ValueError(
+ "List signatures is limited to %d keyids simultaneously"
+ % self._batch_limit)
+
+ args = ["--with-colons", "--fixed-list-mode", "--list-sigs"]
+
+ for key in keyids:
+ args.append(key)
+
+ proc = self._open_subprocess(args)
+ result = self._result_map['list'](self)
+ self._collect_output(proc, result, stdin=proc.stdin)
+ return result
+
+ def gen_key(self, input):
+ """Generate a GnuPG key through batch file key generation. See
+ :meth:`GPG.gen_key_input()` for creating the control input.
+
+ >>> import gnupg
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_input = gpg.gen_key_input()
+ >>> key = gpg.gen_key(key_input)
+ >>> assert key.fingerprint
+
+ :param dict input: A dictionary of parameters and values for the new
+ key.
+ :returns: The result mapping with details of the new key, which is a
+ :class:`GenKey <gnupg._parsers.GenKey>` object.
+ """
+ args = ["--gen-key --batch"]
+ key = self._result_map['generate'](self)
+ f = _make_binary_stream(input, self._encoding)
+ self._handle_io(args, f, key, binary=True)
+ f.close()
+
+ fpr = str(key.fingerprint)
+ if len(fpr) == 20:
+ for d in map(lambda x: os.path.dirname(x),
+ [self.temp_keyring, self.temp_secring]):
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ if self.temp_keyring:
+ if os.path.isfile(self.temp_keyring):
+ prefix = os.path.join(self.temp_keyring, fpr)
+ try: os.rename(self.temp_keyring, prefix+".pubring")
+ except OSError as ose: log.error(str(ose))
+
+ if self.temp_secring:
+ if os.path.isfile(self.temp_secring):
+ prefix = os.path.join(self.temp_secring, fpr)
+ try: os.rename(self.temp_secring, prefix+".secring")
+ except OSError as ose: log.error(str(ose))
+
+ log.info("Key created. Fingerprint: %s" % fpr)
+ key.keyring = self.temp_keyring
+ key.secring = self.temp_secring
+ self.temp_keyring = None
+ self.temp_secring = None
+
+ return key
+
+ def gen_key_input(self, separate_keyring=False, save_batchfile=False,
+ testing=False, **kwargs):
+ """Generate a batch file for input to :meth:`~gnupg.GPG.gen_key`.
+
+ The GnuPG batch file key generation feature allows unattended key
+ generation by creating a file with special syntax and then providing it
+ to: :command:`gpg --gen-key --batch`. Batch files look like this:
+
+ | Name-Real: Alice
+ | Name-Email: alice@inter.net
+ | Expire-Date: 2014-04-01
+ | Key-Type: RSA
+ | Key-Length: 4096
+ | Key-Usage: cert
+ | Subkey-Type: RSA
+ | Subkey-Length: 4096
+ | Subkey-Usage: encrypt,sign,auth
+ | Passphrase: sekrit
+ | %pubring foo.gpg
+ | %secring sec.gpg
+ | %commit
+
+ which is what this function creates for you. All of the available,
+ non-control parameters are detailed below (control parameters are the
+ ones which begin with a '%'). For example, to generate the batch file
+ example above, use like this:
+
+ >>> import gnupg
+ GnuPG logging disabled...
+ >>> from __future__ import print_function
+ >>> gpg = gnupg.GPG(homedir='doctests')
+ >>> alice = { 'name_real': 'Alice',
+ ... 'name_email': 'alice@inter.net',
+ ... 'expire_date': '2014-04-01',
+ ... 'key_type': 'RSA',
+ ... 'key_length': 4096,
+ ... 'key_usage': '',
+ ... 'subkey_type': 'RSA',
+ ... 'subkey_length': 4096,
+ ... 'subkey_usage': 'encrypt,sign,auth',
+ ... 'passphrase': 'sekrit'}
+ >>> alice_input = gpg.gen_key_input(**alice)
+ >>> print(alice_input)
+ Key-Type: RSA
+ Subkey-Type: RSA
+ Subkey-Usage: encrypt,sign,auth
+ Expire-Date: 2014-04-01
+ Passphrase: sekrit
+ Name-Real: Alice
+ Name-Email: alice@inter.net
+ Key-Length: 4096
+ Subkey-Length: 4096
+ %pubring ./doctests/alice.pubring.gpg
+ %secring ./doctests/alice.secring.gpg
+ %commit
+ <BLANKLINE>
+ >>> alice_key = gpg.gen_key(alice_input)
+ >>> assert alice_key is not None
+ >>> assert alice_key.fingerprint is not None
+ >>> message = "no one else can read my sekrit message"
+ >>> encrypted = gpg.encrypt(message, alice_key.fingerprint)
+ >>> assert isinstance(encrypted.data, str)
+
+ :param bool separate_keyring: Specify for the new key to be written to
+ a separate pubring.gpg and secring.gpg. If True,
+ :meth:`~gnupg.GPG.gen_key` will automatically rename the separate
+ keyring and secring to whatever the fingerprint of the generated
+ key ends up being, suffixed with '.pubring' and '.secring'
+ respectively.
+
+ :param bool save_batchfile: Save a copy of the generated batch file to
+ disk in a file named <name_real>.batch, where <name_real> is the
+ ``name_real`` parameter stripped of punctuation, spaces, and
+ non-ascii characters.
+
+ :param bool testing: Uses a faster, albeit insecure random number
+ generator to create keys. This should only be used for testing
+ purposes, for keys which are going to be created and then soon
+ after destroyed, and never for the generation of actual use keys.
+
+ :param str name_real: The name field of the UID in the generated key.
+ :param str name_comment: The comment in the UID of the generated key.
+
+ :param str name_email: The email in the UID of the generated key.
+ (default: ``$USER`` @ :command:`hostname` ) Remember to use UTF-8
+ encoding for the entirety of the UID. At least one of
+ ``name_real``, ``name_comment``, or ``name_email`` must be
+ provided, or else no user ID is created.
+
+ :param str key_type: One of 'RSA', 'DSA', 'ELG-E', or 'default'.
+ (default: 'RSA', if using GnuPG v1.x, otherwise 'default') Starts
+ a new parameter block by giving the type of the primary key. The
+ algorithm must be capable of signing. This is a required
+ parameter. The algorithm may either be an OpenPGP algorithm number
+ or a string with the algorithm name. The special value ‘default’
+ may be used for algo to create the default key type; in this case
+ a ``key_usage`` should not be given and 'default' must also be
+ used for ``subkey_type``.
+
+ :param int key_length: The requested length of the generated key in
+ bits. (Default: 4096)
+
+ :param str key_grip: hexstring This is an optional hexidecimal string
+ which is used to generate a CSR or certificate for an already
+ existing key. ``key_length`` will be ignored if this parameter
+ is given.
+
+ :param str key_usage: Space or comma delimited string of key
+ usages. Allowed values are ‘encrypt’, ‘sign’, and ‘auth’. This is
+ used to generate the key flags. Please make sure that the
+ algorithm is capable of this usage. Note that OpenPGP requires
+ that all primary keys are capable of certification, so no matter
+ what usage is given here, the ‘cert’ flag will be on. If no
+ ‘Key-Usage’ is specified and the ‘Key-Type’ is not ‘default’, all
+ allowed usages for that particular algorithm are used; if it is
+ not given but ‘default’ is used the usage will be ‘sign’.
+
+ :param str subkey_type: This generates a secondary key
+ (subkey). Currently only one subkey can be handled. See also
+ ``key_type`` above.
+
+ :param int subkey_length: The length of the secondary subkey in bits.
+
+ :param str subkey_usage: Key usage for a subkey; similar to
+ ``key_usage``.
+
+ :type expire_date: :obj:`int` or :obj:`str`
+ :param expire_date: Can be specified as an iso-date or as
+ <int>[d|w|m|y] Set the expiration date for the key (and the
+ subkey). It may either be entered in ISO date format (2000-08-15)
+ or as number of days, weeks, month or years. The special notation
+ "seconds=N" is also allowed to directly give an Epoch
+ value. Without a letter days are assumed. Note that there is no
+ check done on the overflow of the type used by OpenPGP for
+ timestamps. Thus you better make sure that the given value make
+ sense. Although OpenPGP works with time intervals, GnuPG uses an
+ absolute value internally and thus the last year we can represent
+ is 2105.
+
+ :param str creation_date: Set the creation date of the key as stored
+ in the key information and which is also part of the fingerprint
+ calculation. Either a date like "1986-04-26" or a full timestamp
+ like "19860426T042640" may be used. The time is considered to be
+ UTC. If it is not given the current time is used.
+
+ :param str passphrase: The passphrase for the new key. The default is
+ to not use any passphrase. Note that GnuPG>=2.1.x will not allow
+ you to specify a passphrase for batch key generation -- GnuPG will
+ ignore the **passphrase** parameter, stop, and ask the user for
+ the new passphrase. However, we can put the command
+ ``%no-protection`` into the batch key generation file to allow a
+ passwordless key to be created, which can then have its passphrase
+ set later with ``--edit-key``.
+
+ :param str preferences: Set the cipher, hash, and compression
+ preference values for this key. This expects the same type of
+ string as the sub-command ‘setpref’ in the --edit-key menu.
+
+ :param str revoker: Should be given as 'algo:fpr' (case sensitive).
+ Add a designated revoker to the generated key. Algo is the public
+ key algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
+ fpr is the fingerprint of the designated revoker. The optional
+ ‘sensitive’ flag marks the designated revoker as sensitive
+ information. Only v4 keys may be designated revokers.
+
+ :param str keyserver: This is an optional parameter that specifies the
+ preferred keyserver URL for the key.
+
+ :param str handle: This is an optional parameter only used with the
+ status lines ``KEY_CREATED`` and ``KEY_NOT_CREATED``. string may
+ be up to 100 characters and should not contain spaces. It is
+ useful for batch key generation to associate a key parameter block
+ with a status line.
+
+ :rtype: str
+ :returns: A suitable input string for the :meth:`GPG.gen_key` method,
+ the latter of which will create the new keypair.
+
+ See `this GnuPG Manual section`__ for more details.
+
+ __ http://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
+ """
+ #: A boolean for determining whether to set subkey_type to 'default'
+ default_type = False
+
+ parms = {}
+
+ ## if using GnuPG version 1.x, then set the default 'Key-Type' to
+ ## 'RSA' because it doesn't understand 'default'
+ parms.setdefault('Key-Type', 'default')
+ if _util._is_gpg1(self.binary_version):
+ parms.setdefault('Key-Type', 'RSA')
+ log.debug("GnuPG v%s detected: setting default key type to %s."
+ % (self.binary_version, parms['Key-Type']))
+ parms.setdefault('Key-Length', 4096)
+ parms.setdefault('Name-Real', "Autogenerated Key")
+ parms.setdefault('Expire-Date', _util._next_year())
+
+ name_email = kwargs.get('name_email')
+ uidemail = _util.create_uid_email(name_email)
+ parms.setdefault('Name-Email', uidemail)
+
+ if testing:
+ ## This specific comment string is required by (some? all?)
+ ## versions of GnuPG to use the insecure PRNG:
+ parms.setdefault('Name-Comment', 'insecure!')
+
+ for key, val in list(kwargs.items()):
+ key = key.replace('_','-').title()
+ ## to set 'cert', 'Key-Usage' must be blank string
+ if not key in ('Key-Usage', 'Subkey-Usage'):
+ if str(val).strip():
+ parms[key] = val
+
+ ## if Key-Type is 'default', make Subkey-Type also be 'default'
+ if parms['Key-Type'] == 'default':
+ default_type = True
+ for field in ('Key-Usage', 'Subkey-Usage',):
+ try: parms.pop(field) ## toss these out, handle manually
+ except KeyError: pass
+
+ ## Key-Type must come first, followed by length
+ out = "Key-Type: %s\n" % parms.pop('Key-Type')
+ out += "Key-Length: %d\n" % parms.pop('Key-Length')
+ if 'Subkey-Type' in parms.keys():
+ out += "Subkey-Type: %s\n" % parms.pop('Subkey-Type')
+ else:
+ if default_type:
+ out += "Subkey-Type: default\n"
+ if 'Subkey-Length' in parms.keys():
+ out += "Subkey-Length: %s\n" % parms.pop('Subkey-Length')
+
+ for key, val in list(parms.items()):
+ out += "%s: %s\n" % (key, val)
+
+ ## There is a problem where, in the batch files, if the '%%pubring'
+ ## and '%%secring' are given as any static string, i.e. 'pubring.gpg',
+ ## that file will always get rewritten without confirmation, killing
+ ## off any keys we had before. So in the case where we wish to
+ ## generate a bunch of keys and then do stuff with them, we should not
+ ## give 'pubring.gpg' as our keyring file, otherwise we will lose any
+ ## keys we had previously.
+
+ if separate_keyring:
+ ring = str(uidemail + '_' + str(_util._utc_epoch()))
+ self.temp_keyring = os.path.join(self.homedir, ring+'.pubring')
+ self.temp_secring = os.path.join(self.homedir, ring+'.secring')
+ out += "%%pubring %s\n" % self.temp_keyring
+ out += "%%secring %s\n" % self.temp_secring
+
+ if testing:
+ ## see TODO file, tag :compatibility:gen_key_input:
+ ##
+ ## Add version detection before the '%no-protection' flag.
+ out += "%no-protection\n"
+ out += "%transient-key\n"
+
+ out += "%commit\n"
+
+ ## if we've been asked to save a copy of the batch file:
+ if save_batchfile and parms['Name-Email'] != uidemail:
+ asc_uid = encodings.normalize_encoding(parms['Name-Email'])
+ filename = _fix_unsafe(asc_uid) + _util._now() + '.batch'
+ save_as = os.path.join(self._batch_dir, filename)
+ readme = os.path.join(self._batch_dir, 'README')
+
+ if not os.path.exists(self._batch_dir):
+ os.makedirs(self._batch_dir)
+
+ ## the following pulls the link to GnuPG's online batchfile
+ ## documentation from this function's docstring and sticks it
+ ## in a README file in the batch directory:
+
+ if getattr(self.gen_key_input, '__doc__', None) is not None:
+ docs = self.gen_key_input.__doc__
+ else:
+ docs = str() ## docstring=None if run with "python -OO"
+ links = '\n'.join(x.strip() for x in docs.splitlines()[-2:])
+ explain = """
+This directory was created by python-gnupg, on {}, and
+it contains saved batch files, which can be given to GnuPG to automatically
+generate keys. Please see
+{}""".format(_util.now(), links) ## sometimes python is awesome.
+
+ with open(readme, 'a+') as fh:
+ [fh.write(line) for line in explain]
+
+ with open(save_as, 'a+') as batch_file:
+ [batch_file.write(line) for line in out]
+
+ return out
+
+ def encrypt(self, data, *recipients, **kwargs):
+ """Encrypt the message contained in ``data`` to ``recipients``.
+
+ :param str data: The file or bytestream to encrypt.
+
+ :param str recipients: The recipients to encrypt to. Recipients must
+ be specified keyID/fingerprint. Care should be taken in Python2.x
+ to make sure that the given fingerprint is in fact a string and
+ not a unicode object.
+
+ :param str default_key: The keyID/fingerprint of the key to use for
+ signing. If given, ``data`` will be encrypted and signed.
+
+ :param str passphrase: If given, and ``default_key`` is also given,
+ use this passphrase to unlock the secret portion of the
+ ``default_key`` to sign the encrypted ``data``. Otherwise, if
+ ``default_key`` is not given, but ``symmetric=True``, then use
+ this passphrase as the passphrase for symmetric
+ encryption. Signing and symmetric encryption should *not* be
+ combined when sending the ``data`` to other recipients, else the
+ passphrase to the secret key would be shared with them.
+
+ :param bool armor: If True, ascii armor the output; otherwise, the
+ output will be in binary format. (Default: True)
+
+ :param bool encrypt: If True, encrypt the ``data`` using the
+ ``recipients`` public keys. (Default: True)
+
+ :param bool symmetric: If True, encrypt the ``data`` to ``recipients``
+ using a symmetric key. See the ``passphrase`` parameter. Symmetric
+ encryption and public key encryption can be used simultaneously,
+ and will result in a ciphertext which is decryptable with either
+ the symmetric ``passphrase`` or one of the corresponding private
+ keys.
+
+ :param bool always_trust: If True, ignore trust warnings on recipient
+ keys. If False, display trust warnings. (default: True)
+
+ :param str output: The output file to write to. If not specified, the
+ encrypted output is returned, and thus should be stored as an
+ object in Python. For example:
+
+ >>> import shutil
+ >>> import gnupg
+ >>> if os.path.exists("doctests"):
+ ... shutil.rmtree("doctests")
+ >>> gpg = gnupg.GPG(homedir="doctests")
+ >>> key_settings = gpg.gen_key_input(key_type='RSA',
+ ... key_length=1024,
+ ... key_usage='ESCA',
+ ... passphrase='foo')
+ >>> key = gpg.gen_key(key_settings)
+ >>> message = "The crow flies at midnight."
+ >>> encrypted = str(gpg.encrypt(message, key.printprint))
+ >>> assert encrypted != message
+ >>> assert not encrypted.isspace()
+ >>> decrypted = str(gpg.decrypt(encrypted))
+ >>> assert not decrypted.isspace()
+ >>> decrypted
+ 'The crow flies at midnight.'
+
+
+ :param str cipher_algo: The cipher algorithm to use. To see available
+ algorithms with your version of GnuPG, do:
+ :command:`$ gpg --with-colons --list-config ciphername`.
+ The default ``cipher_algo``, if unspecified, is ``'AES256'``.
+
+ :param str digest_algo: The hash digest to use. Again, to see which
+ hashes your GnuPG is capable of using, do:
+ :command:`$ gpg --with-colons --list-config digestname`.
+ The default, if unspecified, is ``'SHA512'``.
+
+ :param str compress_algo: The compression algorithm to use. Can be one
+ of ``'ZLIB'``, ``'BZIP2'``, ``'ZIP'``, or ``'Uncompressed'``.
+
+ .. seealso:: :meth:`._encrypt`
+ """
+ stream = _make_binary_stream(data, self._encoding)
+ result = self._encrypt(stream, recipients, **kwargs)
+ stream.close()
+ return result
+
+ def decrypt(self, message, **kwargs):
+ """Decrypt the contents of a string or file-like object ``message``.
+
+ :type message: file or str or :class:`io.BytesIO`
+ :param message: A string or file-like object to decrypt.
+ :param bool always_trust: Instruct GnuPG to ignore trust checks.
+ :param str passphrase: The passphrase for the secret key used for decryption.
+ :param str output: A filename to write the decrypted output to.
+ """
+ stream = _make_binary_stream(message, self._encoding)
+ result = self.decrypt_file(stream, **kwargs)
+ stream.close()
+ return result
+
+ def decrypt_file(self, filename, always_trust=False, passphrase=None,
+ output=None):
+ """Decrypt the contents of a file-like object ``filename`` .
+
+ :param str filename: A file-like object to decrypt.
+ :param bool always_trust: Instruct GnuPG to ignore trust checks.
+ :param str passphrase: The passphrase for the secret key used for decryption.
+ :param str output: A filename to write the decrypted output to.
+ """
+ args = ["--decrypt"]
+ if output: # write the output to a file with the specified name
+ if os.path.exists(output):
+ os.remove(output) # to avoid overwrite confirmation message
+ args.append('--output %s' % output)
+ if always_trust:
+ args.append("--always-trust")
+ result = self._result_map['crypt'](self)
+ self._handle_io(args, filename, result, passphrase, binary=True)
+ log.debug('decrypt result: %r', result.data)
+ return result
+
+class GPGUtilities(object):
+ """Extra tools for working with GnuPG."""
+
+ def __init__(self, gpg):
+ """Initialise extra utility functions."""
+ self._gpg = gpg
+
+ def find_key_by_email(self, email, secret=False):
+ """Find user's key based on their email address.
+
+ :param str email: The email address to search for.
+ :param bool secret: If True, search through secret keyring.
+ """
+ for key in self.list_keys(secret=secret):
+ for uid in key['uids']:
+ if re.search(email, uid):
+ return key
+ raise LookupError("GnuPG public key for email %s not found!" % email)
+
+ def find_key_by_subkey(self, subkey):
+ """Find a key by a fingerprint of one of its subkeys.
+
+ :param str subkey: The fingerprint of the subkey to search for.
+ """
+ for key in self.list_keys():
+ for sub in key['subkeys']:
+ if sub[0] == subkey:
+ return key
+ raise LookupError(
+ "GnuPG public key for subkey %s not found!" % subkey)
+
+ def send_keys(self, keyserver, *keyids):
+ """Send keys to a keyserver."""
+ result = self._result_map['list'](self)
+ log.debug('send_keys: %r', keyids)
+ data = _util._make_binary_stream("", self._encoding)
+ args = ['--keyserver', keyserver, '--send-keys']
+ args.extend(keyids)
+ self._handle_io(args, data, result, binary=True)
+ log.debug('send_keys result: %r', result.__dict__)
+ data.close()
+ return result
+
+ def encrypted_to(self, raw_data):
+ """Return the key to which raw_data is encrypted to."""
+ # TODO: make this support multiple keys.
+ result = self._gpg.list_packets(raw_data)
+ if not result.key:
+ raise LookupError(
+ "Content is not encrypted to a GnuPG key!")
+ try:
+ return self.find_key_by_keyid(result.key)
+ except:
+ return self.find_key_by_subkey(result.key)
+
+ def is_encrypted_sym(self, raw_data):
+ result = self._gpg.list_packets(raw_data)
+ return bool(result.need_passphrase_sym)
+
+ def is_encrypted_asym(self, raw_data):
+ result = self._gpg.list_packets(raw_data)
+ return bool(result.key)
+
+ def is_encrypted(self, raw_data):
+ return self.is_encrypted_asym(raw_data) or self.is_encrypted_sym(raw_data)
+
+if __name__ == "__main__":
+ from .test import test_gnupg
+ test_gnupg.main()