aboutsummaryrefslogtreecommitdiffstats
path: root/lang/python/docs
diff options
context:
space:
mode:
Diffstat (limited to 'lang/python/docs')
-rw-r--r--lang/python/docs/GPGMEpythonHOWTOen.org2486
1 files changed, 1235 insertions, 1251 deletions
diff --git a/lang/python/docs/GPGMEpythonHOWTOen.org b/lang/python/docs/GPGMEpythonHOWTOen.org
index 5fa01365..106b624e 100644
--- a/lang/python/docs/GPGMEpythonHOWTOen.org
+++ b/lang/python/docs/GPGMEpythonHOWTOen.org
@@ -14,14 +14,14 @@
:CUSTOM_ID: intro
:END:
- | Version: | 0.1.2 |
- | Author: | Ben McGinnes <[email protected]> |
- | Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
- | Language: | Australian English, British English |
- | xml:lang: | en-AU, en-GB, en |
+| Version: | 0.1.3 |
+| Author: | Ben McGinnes <[email protected]> |
+| Author GPG Key: | DB4724E6FA4286C92B4E55C4321E4E2373590E5D |
+| Language: | Australian English, British English |
+| xml:lang: | en-AU, en-GB, en |
- This document provides basic instruction in how to use the GPGME
- Python bindings to programmatically leverage the GPGME library.
+This document provides basic instruction in how to use the GPGME
+Python bindings to programmatically leverage the GPGME library.
** Python 2 versus Python 3
@@ -29,25 +29,25 @@
:CUSTOM_ID: py2-vs-py3
:END:
- Though the GPGME Python bindings themselves provide support for
- both Python 2 and 3, the focus is unequivocally on Python 3 and
- specifically from Python 3.4 and above. As a consequence all the
- examples and instructions in this guide use Python 3 code.
+Though the GPGME Python bindings themselves provide support for both
+Python 2 and 3, the focus is unequivocally on Python 3 and
+specifically from Python 3.4 and above. As a consequence all the
+examples and instructions in this guide use Python 3 code.
- Much of it will work with Python 2, but much of it also deals with
- Python 3 byte literals, particularly when reading and writing data.
- Developers concentrating on Python 2.7, and possibly even 2.6, will
- need to make the appropriate modifications to support the older
- string and unicode types as opposed to bytes.
+Much of it will work with Python 2, but much of it also deals with
+Python 3 byte literals, particularly when reading and writing data.
+Developers concentrating on Python 2.7, and possibly even 2.6, will
+need to make the appropriate modifications to support the older string
+and unicode types as opposed to bytes.
- There are multiple reasons for concentrating on Python 3; some of
- which relate to the immediate integration of these bindings, some
- of which relate to longer term plans for both GPGME and the python
- bindings and some of which relate to the impending EOL period for
- Python 2.7. Essentially, though, there is little value in tying
- the bindings to a version of the language which is a dead end and
- the advantages offered by Python 3 over Python 2 make handling the
- data types with which GPGME deals considerably easier.
+There are multiple reasons for concentrating on Python 3; some of
+which relate to the immediate integration of these bindings, some of
+which relate to longer term plans for both GPGME and the python
+bindings and some of which relate to the impending EOL period for
+Python 2.7. Essentially, though, there is little value in tying the
+bindings to a version of the language which is a dead end and the
+advantages offered by Python 3 over Python 2 make handling the data
+types with which GPGME deals considerably easier.
** Examples
@@ -55,8 +55,8 @@
:CUSTOM_ID: howto-python3-examples
:END:
- All of the examples found in this document can be found as Python 3
- scripts in the =lang/python/examples/howto= directory.
+All of the examples found in this document can be found as Python 3
+scripts in the =lang/python/examples/howto= directory.
* GPGME Concepts
@@ -70,19 +70,17 @@
:CUSTOM_ID: gpgme-c-api
:END:
- Unlike many modern APIs with which programmers will be more
- familiar with these days, the GPGME API is a C API. The API is
- intended for use by C coders who would be able to access its
- features by including the =gpgme.h= header file with their own C
- source code and then access its functions just as they would any
- other C headers.
+Unlike many modern APIs with which programmers will be more familiar
+with these days, the GPGME API is a C API. The API is intended for
+use by C coders who would be able to access its features by including
+the =gpgme.h= header file with their own C source code and then access
+its functions just as they would any other C headers.
- This is a very effective method of gaining complete access to the
- API and in the most efficient manner possible. It does, however,
- have the drawback that it cannot be directly used by other
- languages without some means of providing an interface to those
- languages. This is where the need for bindings in various
- languages stems.
+This is a very effective method of gaining complete access to the API
+and in the most efficient manner possible. It does, however, have the
+drawback that it cannot be directly used by other languages without
+some means of providing an interface to those languages. This is
+where the need for bindings in various languages stems.
** Python bindings
@@ -90,16 +88,16 @@
:CUSTOM_ID: gpgme-python-bindings
:END:
- The Python bindings for GPGME provide a higher level means of
- accessing the complete feature set of GPGME itself. It also
- provides a more pythonic means of calling these API functions.
+The Python bindings for GPGME provide a higher level means of
+accessing the complete feature set of GPGME itself. It also provides
+a more pythonic means of calling these API functions.
- The bindings are generated dynamically with SWIG and the copy of
- =gpgme.h= generated when GPGME is compiled.
+The bindings are generated dynamically with SWIG and the copy of
+=gpgme.h= generated when GPGME is compiled.
- This means that a version of the Python bindings is fundamentally
- tied to the exact same version of GPGME used to generate that copy
- of =gpgme.h=.
+This means that a version of the Python bindings is fundamentally tied
+to the exact same version of GPGME used to generate that copy of
+=gpgme.h=.
** Difference between the Python bindings and other GnuPG Python packages
@@ -107,9 +105,9 @@
:CUSTOM_ID: gpgme-python-bindings-diffs
:END:
- There have been numerous attempts to add GnuPG support to Python
- over the years. Some of the most well known are listed here, along
- with what differentiates them.
+There have been numerous attempts to add GnuPG support to Python over
+the years. Some of the most well known are listed here, along with
+what differentiates them.
*** The python-gnupg package maintained by Vinay Sajip
@@ -117,23 +115,23 @@
:CUSTOM_ID: diffs-python-gnupg
:END:
- This is arguably the most popular means of integrating GPG with
- Python. The package utilises the =subprocess= module to implement
- wrappers for the =gpg= and =gpg2= executables normally invoked on
- the command line (=gpg.exe= and =gpg2.exe= on Windows).
+This is arguably the most popular means of integrating GPG with
+Python. The package utilises the =subprocess= module to implement
+wrappers for the =gpg= and =gpg2= executables normally invoked on the
+command line (=gpg.exe= and =gpg2.exe= on Windows).
- The popularity of this package stemmed from its ease of use and
- capability in providing the most commonly required features.
+The popularity of this package stemmed from its ease of use and
+capability in providing the most commonly required features.
- Unfortunately it has been beset by a number of security issues in
- the past; most of which stemmed from using unsafe methods of
- accessing the command line via the =subprocess= calls. While some
- effort has been made over the last two to three years (as of 2018)
- to mitigate this, particularly by no longer providing shell access
- through those subprocess calls, the wrapper is still somewhat
- limited in the scope of its GnuPG features coverage.
+Unfortunately it has been beset by a number of security issues in the
+past; most of which stemmed from using unsafe methods of accessing the
+command line via the =subprocess= calls. While some effort has been
+made over the last two to three years (as of 2018) to mitigate this,
+particularly by no longer providing shell access through those
+subprocess calls, the wrapper is still somewhat limited in the scope
+of its GnuPG features coverage.
- The python-gnupg package is available under the MIT license.
+The python-gnupg package is available under the MIT license.
*** The gnupg package created and maintained by Isis Lovecruft
@@ -141,20 +139,20 @@
:CUSTOM_ID: diffs-isis-gnupg
:END:
- In 2015 Isis Lovecruft from the Tor Project forked and then
- re-implemented the python-gnupg package as just gnupg. This new
- package also relied on subprocess to call the =gpg= or =gpg2=
- binaries, but did so somewhat more securely.
+In 2015 Isis Lovecruft from the Tor Project forked and then
+re-implemented the python-gnupg package as just gnupg. This new
+package also relied on subprocess to call the =gpg= or =gpg2=
+binaries, but did so somewhat more securely.
- The naming and version numbering selected for this package,
- however, resulted in conflicts with the original python-gnupg and
- since its functions were called in a different manner to
- python-gnupg, the release of this package also resulted in a great
- deal of consternation when people installed what they thought was
- an upgrade that subsequently broke the code relying on it.
+The naming and version numbering selected for this package, however,
+resulted in conflicts with the original python-gnupg and since its
+functions were called in a different manner to python-gnupg, the
+release of this package also resulted in a great deal of consternation
+when people installed what they thought was an upgrade that
+subsequently broke the code relying on it.
- The gnupg package is available under the GNU General Public
- License version 3.0 (or any later version).
+The gnupg package is available under the GNU General Public License
+version 3.0 (or any later version).
*** The PyME package maintained by Martin Albrecht
@@ -162,26 +160,26 @@
:CUSTOM_ID: diffs-pyme
:END:
- This package is the origin of these bindings, though they are
- somewhat different now. For details of when and how the PyME
- package was folded back into GPGME itself see the /Short History/
- document[fn:1] in the Python bindings =docs= directory.[fn:2]
+This package is the origin of these bindings, though they are somewhat
+different now. For details of when and how the PyME package was
+folded back into GPGME itself see the /Short History/ document[fn:1]
+in the Python bindings =docs= directory.[fn:2]
- The PyME package was first released in 2002 and was also the first
- attempt to implement a low level binding to GPGME. In doing so it
- provided access to considerably more functionality than either the
- =python-gnupg= or =gnupg= packages.
+The PyME package was first released in 2002 and was also the first
+attempt to implement a low level binding to GPGME. In doing so it
+provided access to considerably more functionality than either the
+=python-gnupg= or =gnupg= packages.
- The PyME package is only available for Python 2.6 and 2.7.
+The PyME package is only available for Python 2.6 and 2.7.
- Porting the PyME package to Python 3.4 in 2015 is what resulted in
- it being folded into the GPGME project and the current bindings
- are the end result of that effort.
+Porting the PyME package to Python 3.4 in 2015 is what resulted in it
+being folded into the GPGME project and the current bindings are the
+end result of that effort.
- The PyME package is available under the same dual licensing as
- GPGME itself: the GNU General Public License version 2.0 (or any
- later version) and the GNU Lesser General Public License version
- 2.1 (or any later version).
+The PyME package is available under the same dual licensing as GPGME
+itself: the GNU General Public License version 2.0 (or any later
+version) and the GNU Lesser General Public License version 2.1 (or any
+later version).
* GPGME Python bindings installation
@@ -195,19 +193,18 @@
:CUSTOM_ID: do-not-use-pypi
:END:
- Most third-party Python packages and modules are available and
- distributed through the Python Package Installer, known as PyPI.
+Most third-party Python packages and modules are available and
+distributed through the Python Package Installer, known as PyPI.
- Due to the nature of what these bindings are and how they work, it
- is infeasible to install the GPGME Python bindings in the same way.
+Due to the nature of what these bindings are and how they work, it is
+infeasible to install the GPGME Python bindings in the same way.
- This is because the bindings use SWIG to dynamically generate C
- bindings against =gpgme.h= and =gpgme.h= is generated from
- =gpgme.h.in= at compile time when GPGME is built from source. Thus
- to include a package in PyPI which actually built correctly would
- require either statically built libraries for every architecture
- bundled with it or a full implementation of C for each
- architecture.
+This is because the bindings use SWIG to dynamically generate C
+bindings against =gpgme.h= and =gpgme.h= is generated from
+=gpgme.h.in= at compile time when GPGME is built from source. Thus to
+include a package in PyPI which actually built correctly would require
+either statically built libraries for every architecture bundled with
+it or a full implementation of C for each architecture.
** Requirements
@@ -215,14 +212,13 @@
:CUSTOM_ID: gpgme-python-requirements
:END:
- The GPGME Python bindings only have three requirements:
+The GPGME Python bindings only have three requirements:
- 1. A suitable version of Python 2 or Python 3. With Python 2 that
- means Python 2.7 and with Python 3 that means Python 3.4 or
- higher.
- 2. SWIG.
- 3. GPGME itself. Which also means that all of GPGME's dependencies
- must be installed too.
+1. A suitable version of Python 2 or Python 3. With Python 2 that
+ means Python 2.7 and with Python 3 that means Python 3.4 or higher.
+2. SWIG.
+3. GPGME itself. Which also means that all of GPGME's dependencies
+ must be installed too.
** Installation
@@ -230,24 +226,23 @@
:CUSTOM_ID: installation
:END:
- Installing the Python bindings is effectively achieved by compiling
- and installing GPGME itself.
+Installing the Python bindings is effectively achieved by compiling
+and installing GPGME itself.
- Once SWIG is installed with Python and all the dependencies for
- GPGME are installed you only need to confirm that the version(s) of
- Python you want the bindings installed for are in your =$PATH=.
+Once SWIG is installed with Python and all the dependencies for GPGME
+are installed you only need to confirm that the version(s) of Python
+you want the bindings installed for are in your =$PATH=.
- By default GPGME will attempt to install the bindings for the most
- recent or highest version number of Python 2 and Python 3 it
- detects in =$PATH=. It specifically checks for the =python= and
- =python3= executables first and then checks for specific version
- numbers.
+By default GPGME will attempt to install the bindings for the most
+recent or highest version number of Python 2 and Python 3 it detects
+in =$PATH=. It specifically checks for the =python= and =python3=
+executables first and then checks for specific version numbers.
- For Python 2 it checks for these executables in this order:
- =python=, =python2= and =python2.7=.
+For Python 2 it checks for these executables in this order: =python=,
+=python2= and =python2.7=.
- For Python 3 it checks for these executables in this order:
- =python3=, =python3.6=, =python3.5=, =python3.4= and =python3.7=.[fn:4]
+For Python 3 it checks for these executables in this order: =python3=,
+=python3.6=, =python3.5=, =python3.4= and =python3.7=.[fn:4]
*** Installing GPGME
@@ -255,8 +250,8 @@
:CUSTOM_ID: install-gpgme
:END:
- See the GPGME =README= file for details of how to install GPGME from
- source.
+See the GPGME =README= file for details of how to install GPGME from
+source.
* Fundamentals
@@ -264,9 +259,9 @@
:CUSTOM_ID: howto-fund-a-mental
:END:
- Before we can get to the fun stuff, there are a few matters
- regarding GPGME's design which hold true whether you're dealing with
- the C code directly or these Python bindings.
+Before we can get to the fun stuff, there are a few matters regarding
+GPGME's design which hold true whether you're dealing with the C code
+directly or these Python bindings.
** No REST
@@ -274,23 +269,23 @@
:CUSTOM_ID: no-rest-for-the-wicked
:END:
- The first part of which is or will be fairly blatantly obvious upon
- viewing the first example, but it's worth reiterating anyway. That
- being that this API is /*not*/ a REST API. Nor indeed could it
- ever be one.
+The first part of which is or will be fairly blatantly obvious upon
+viewing the first example, but it's worth reiterating anyway. That
+being that this API is /*not*/ a REST API. Nor indeed could it ever
+be one.
- Most, if not all, Python programmers (and not just Python
- programmers) know how easy it is to work with a RESTful API. In
- fact they've become so popular that many other APIs attempt to
- emulate REST-like behaviour as much as they are able. Right down
- to the use of JSON formatted output to facilitate the use of their
- API without having to retrain developers.
+Most, if not all, Python programmers (and not just Python programmers)
+know how easy it is to work with a RESTful API. In fact they've
+become so popular that many other APIs attempt to emulate REST-like
+behaviour as much as they are able. Right down to the use of JSON
+formatted output to facilitate the use of their API without having to
+retrain developers.
- This API does not do that. It would not be able to do that and
- also provide access to the entire C API on which it's built. It
- does, however, provide a very pythonic interface on top of the
- direct bindings and it's this pythonic layer with which this HOWTO
- deals with.
+This API does not do that. It would not be able to do that and also
+provide access to the entire C API on which it's built. It does,
+however, provide a very pythonic interface on top of the direct
+bindings and it's this pythonic layer with which this HOWTO deals
+with.
** Context
@@ -298,22 +293,22 @@
:CUSTOM_ID: howto-get-context
:END:
- One of the reasons which prevents this API from being RESTful is
- that most operations require more than one instruction to the API
- to perform the task. Sure, there are certain functions which can
- be performed simultaneously, particularly if the result known or
- strongly anticipated (e.g. selecting and encrypting to a key known
- to be in the public keybox).
+One of the reasons which prevents this API from being RESTful is that
+most operations require more than one instruction to the API to
+perform the task. Sure, there are certain functions which can be
+performed simultaneously, particularly if the result known or strongly
+anticipated (e.g. selecting and encrypting to a key known to be in the
+public keybox).
- There are many more, however, which cannot be manipulated so
- readily: they must be performed in a specific sequence and the
- result of one operation has a direct bearing on the outcome of
- subsequent operations. Not merely by generating an error either.
+There are many more, however, which cannot be manipulated so readily:
+they must be performed in a specific sequence and the result of one
+operation has a direct bearing on the outcome of subsequent
+operations. Not merely by generating an error either.
- When dealing with this type of persistent state on the web, full of
- both the RESTful and REST-like, it's most commonly referred to as a
- session. In GPGME, however, it is called a context and every
- operation type has one.
+When dealing with this type of persistent state on the web, full of
+both the RESTful and REST-like, it's most commonly referred to as a
+session. In GPGME, however, it is called a context and every
+operation type has one.
* Working with keys
@@ -327,58 +322,58 @@
:CUSTOM_ID: howto-keys-selection
:END:
- Selecting keys to encrypt to or to sign with will be a common
- occurrence when working with GPGMe and the means available for
- doing so are quite simple.
+Selecting keys to encrypt to or to sign with will be a common
+occurrence when working with GPGMe and the means available for doing
+so are quite simple.
- They do depend on utilising a Context; however once the data is
- recorded in another variable, that Context does not need to be the
- same one which subsequent operations are performed.
+They do depend on utilising a Context; however once the data is
+recorded in another variable, that Context does not need to be the
+same one which subsequent operations are performed.
- The easiest way to select a specific key is by searching for that
- key's key ID or fingerprint, preferably the full fingerprint
- without any spaces in it. A long key ID will probably be okay, but
- is not advised and short key IDs are already a problem with some
- being generated to match specific patterns. It does not matter
- whether the pattern is upper or lower case.
+The easiest way to select a specific key is by searching for that
+key's key ID or fingerprint, preferably the full fingerprint without
+any spaces in it. A long key ID will probably be okay, but is not
+advised and short key IDs are already a problem with some being
+generated to match specific patterns. It does not matter whether the
+pattern is upper or lower case.
- So this is the best method:
+So this is the best method:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
- keys = list(k)
- #+end_src
+ k = gpg.Context().keylist(pattern="258E88DCBD3CD44D8E7AB43F6ECB6AF0DEADBEEF")
+ keys = list(k)
+#+end_src
- This is passable and very likely to be common:
+This is passable and very likely to be common:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
- keys = list(k)
- #+end_src
+ k = gpg.Context().keylist(pattern="0x6ECB6AF0DEADBEEF")
+ keys = list(k)
+#+end_src
- And this is a really bad idea:
+And this is a really bad idea:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- k = gpg.Context().keylist(pattern="0xDEADBEEF")
- keys = list(k)
- #+end_src
+ k = gpg.Context().keylist(pattern="0xDEADBEEF")
+ keys = list(k)
+#+end_src
- Alternatively it may be that the intention is to create a list of
- keys which all match a particular search string. For instance all
- the addresses at a particular domain, like this:
+Alternatively it may be that the intention is to create a list of keys
+which all match a particular search string. For instance all the
+addresses at a particular domain, like this:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- ncsc = gpg.Context().keylist(pattern="ncsc.mil")
- nsa = list(ncsc)
- #+end_src
+ ncsc = gpg.Context().keylist(pattern="ncsc.mil")
+ nsa = list(ncsc)
+#+end_src
*** Counting keys
@@ -386,29 +381,28 @@
:CUSTOM_ID: howto-keys-counting
:END:
- Counting the number of keys in your public keybox (=pubring.kbx=),
- the format which has superseded the old keyring format
- (=pubring.gpg= and =secring.gpg=), or the number of secret keys is
- a very simple task.
+Counting the number of keys in your public keybox (=pubring.kbx=), the
+format which has superseded the old keyring format (=pubring.gpg= and
+=secring.gpg=), or the number of secret keys is a very simple task.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- c = gpg.Context()
- seckeys = c.keylist(pattern=None, secret=True)
- pubkeys = c.keylist(pattern=None, secret=False)
+ c = gpg.Context()
+ seckeys = c.keylist(pattern=None, secret=True)
+ pubkeys = c.keylist(pattern=None, secret=False)
- seclist = list(seckeys)
- secnum = len(seclist)
+ seclist = list(seckeys)
+ secnum = len(seclist)
- publist = list(pubkeys)
- pubnum = len(publist)
+ publist = list(pubkeys)
+ pubnum = len(publist)
- print("""
- Number of secret keys: {0}
- Number of public keys: {1}
- """.format(secnum, pubnum))
- #+end_src
+ print("""
+ Number of secret keys: {0}
+ Number of public keys: {1}
+ """.format(secnum, pubnum))
+#+end_src
** Get key
@@ -416,42 +410,42 @@
:CUSTOM_ID: howto-get-key
:END:
- An alternative method of getting a single key via its fingerprint
- is available directly within a Context with =Context().get_key=.
- This is the preferred method of selecting a key in order to modify
- it, sign or certify it and for obtaining relevant data about a
- single key as a part of other functions; when verifying a signature
- made by that key, for instance.
+An alternative method of getting a single key via its fingerprint is
+available directly within a Context with =Context().get_key=. This is
+the preferred method of selecting a key in order to modify it, sign or
+certify it and for obtaining relevant data about a single key as a
+part of other functions; when verifying a signature made by that key,
+for instance.
- By default this method will select public keys, but it can select
- secret keys as well.
+By default this method will select public keys, but it can select
+secret keys as well.
- This first example demonstrates selecting the current key of Werner
- Koch, which is due to expire at the end of 2018:
+This first example demonstrates selecting the current key of Werner
+Koch, which is due to expire at the end of 2018:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
- key = gpg.Context().get_key(fingerprint)
- #+end_src
+ fingerprint = "80615870F5BAD690333686D0F2AD85AC1E42B367"
+ key = gpg.Context().get_key(fingerprint)
+#+end_src
- Whereas this example demonstrates selecting the author's current
- key with the =secret= key word argument set to =True=:
+Whereas this example demonstrates selecting the author's current key
+with the =secret= key word argument set to =True=:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
- key = gpg.Context().get_key(fingerprint, secret=True)
- #+end_src
+ fingerprint = "DB4724E6FA4286C92B4E55C4321E4E2373590E5D"
+ key = gpg.Context().get_key(fingerprint, secret=True)
+#+end_src
- It is, of course, quite possible to select expired, disabled and
- revoked keys with this function, but only to effectively display
- information about those keys.
+It is, of course, quite possible to select expired, disabled and
+revoked keys with this function, but only to effectively display
+information about those keys.
- It is also possible to use both unicode or string literals and byte
- literals with the fingerprint when getting a key in this way.
+It is also possible to use both unicode or string literals and byte
+literals with the fingerprint when getting a key in this way.
** Importing keys
@@ -459,65 +453,65 @@
:CUSTOM_ID: howto-import-key
:END:
- Importing keys is possible with the =key_import()= method and takes
- one argument which is a bytes literal object containing either the
- binary or ASCII armoured key data for one or more keys.
-
- The following example retrieves one or more keys from the SKS
- keyservers via the web using the requests module. Since requests
- returns the content as a bytes literal object, we can then use that
- directly to import the resulting data into our keybox.
-
- #+begin_src python
- import gpg
- import os.path
- import requests
-
- c = gpg.Context()
- url = "https://sks-keyservers.net/pks/lookup"
- pattern = input("Enter the pattern to search for key or user IDs: ")
- payload = { "op": "get", "search": pattern }
-
- r = requests.get(url, verify=True, params=payload)
- result = c.key_import(r.content)
-
- if result is not None and hasattr(result, "considered") is False:
- print(result)
- elif result is not None and hasattr(result, "considered") is True:
- num_keys = len(result.imports)
- new_revs = result.new_revocations
- new_sigs = result.new_signatures
- new_subs = result.new_sub_keys
- new_uids = result.new_user_ids
- new_scrt = result.secret_imported
- nochange = result.unchanged
- print("""
- The total number of keys considered for import was: {0}
-
- Number of keys revoked: {1}
- Number of new signatures: {2}
- Number of new subkeys: {3}
- Number of new user IDs: {4}
- Number of new secret keys: {5}
- Number of unchanged keys: {6}
-
- The key IDs for all considered keys were:
- """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
- nochange))
- for i in range(num_keys):
- print(result.imports[i].fpr)
- print("")
- else:
- pass
- #+end_src
-
- *NOTE:* When searching for a key ID of any length or a fingerprint
- (without spaces), the SKS servers require the the leading =0x=
- indicative of hexadecimal be included. Also note that the old short
- key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the
- relative ease by which such key IDs can be reproduced, as
- demonstrated by the Evil32 Project in 2014 (which was subsequently
- exploited in 2016).
+Importing keys is possible with the =key_import()= method and takes
+one argument which is a bytes literal object containing either the
+binary or ASCII armoured key data for one or more keys.
+
+The following example retrieves one or more keys from the SKS
+keyservers via the web using the requests module. Since requests
+returns the content as a bytes literal object, we can then use that
+directly to import the resulting data into our keybox.
+
+#+begin_src python
+ import gpg
+ import os.path
+ import requests
+
+ c = gpg.Context()
+ url = "https://sks-keyservers.net/pks/lookup"
+ pattern = input("Enter the pattern to search for key or user IDs: ")
+ payload = { "op": "get", "search": pattern }
+
+ r = requests.get(url, verify=True, params=payload)
+ result = c.key_import(r.content)
+
+ if result is not None and hasattr(result, "considered") is False:
+ print(result)
+ elif result is not None and hasattr(result, "considered") is True:
+ num_keys = len(result.imports)
+ new_revs = result.new_revocations
+ new_sigs = result.new_signatures
+ new_subs = result.new_sub_keys
+ new_uids = result.new_user_ids
+ new_scrt = result.secret_imported
+ nochange = result.unchanged
+ print("""
+ The total number of keys considered for import was: {0}
+
+ Number of keys revoked: {1}
+ Number of new signatures: {2}
+ Number of new subkeys: {3}
+ Number of new user IDs: {4}
+ Number of new secret keys: {5}
+ Number of unchanged keys: {6}
+
+ The key IDs for all considered keys were:
+ """.format(num_keys, new_revs, new_sigs, new_subs, new_uids, new_scrt,
+ nochange))
+ for i in range(num_keys):
+ print(result.imports[i].fpr)
+ print("")
+ else:
+ pass
+#+end_src
+
+*NOTE:* When searching for a key ID of any length or a fingerprint
+(without spaces), the SKS servers require the the leading =0x=
+indicative of hexadecimal be included. Also note that the old short
+key IDs (e.g. =0xDEADBEEF=) should no longer be used due to the
+relative ease by which such key IDs can be reproduced, as demonstrated
+by the Evil32 Project in 2014 (which was subsequently exploited in
+2016).
** Exporting keys
@@ -525,10 +519,10 @@
:CUSTOM_ID: howto-export-key
:END:
- Exporting keys remains a reasonably simple task, but has been
- separated into three different functions for the OpenPGP
- cryptographic engine. Two of those functions are for exporting
- public keys and the third is for exporting secret keys.
+Exporting keys remains a reasonably simple task, but has been
+separated into three different functions for the OpenPGP cryptographic
+engine. Two of those functions are for exporting public keys and the
+third is for exporting secret keys.
*** Exporting public keys
@@ -536,117 +530,117 @@
:CUSTOM_ID: howto-export-public-key
:END:
- There are two methods of exporting public keys, both of which are
- very similar to the other. The default method, =key_export()=,
- will export a public key or keys matching a specified pattern as
- normal. The alternative, the =key_export_minimal()= method, will
- do the same thing except producing a minimised output with extra
- signatures and third party signatures or certifications removed.
-
- #+begin_src python
- import gpg
- import os.path
- import sys
-
- print("""
- This script exports one or more public keys.
- """)
-
- c = gpg.Context(armor=True)
-
- if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
- elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
- elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
- else:
- keyfile = input("Enter the path and filename to save the secret key to: ")
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
- if homedir.startswith("~"):
- if os.path.exists(os.path.expanduser(homedir)) is True:
- c.home_dir = os.path.expanduser(homedir)
- else:
- pass
- elif os.path.exists(homedir) is True:
- c.home_dir = homedir
+There are two methods of exporting public keys, both of which are very
+similar to the other. The default method, =key_export()=, will export
+a public key or keys matching a specified pattern as normal. The
+alternative, the =key_export_minimal()= method, will do the same thing
+except producing a minimised output with extra signatures and third
+party signatures or certifications removed.
+
+#+begin_src python
+ import gpg
+ import os.path
+ import sys
+
+ print("""
+ This script exports one or more public keys.
+ """)
+
+ c = gpg.Context(armor=True)
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
else:
pass
-
- try:
- result = c.key_export(pattern=logrus)
- except:
- result = c.key_export(pattern=None)
-
- if result is not None:
- with open(keyfile, "wb") as f:
- f.write(result)
- else:
- pass
- #+end_src
-
- It is important to note that the result will only return =None=
- when a pattern has been entered for =logrus=, but it has not
- matched any keys. When the search pattern itself is set to =None=
- this triggers the exporting of the entire public keybox.
-
- #+begin_src python
- import gpg
- import os.path
- import sys
-
- print("""
- This script exports one or more public keys in minimised form.
- """)
-
- c = gpg.Context(armor=True)
-
- if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
- elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
- elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
- else:
- keyfile = input("Enter the path and filename to save the secret key to: ")
- logrus = input("Enter the UID matching the key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
- if homedir.startswith("~"):
- if os.path.exists(os.path.expanduser(homedir)) is True:
- c.home_dir = os.path.expanduser(homedir)
- else:
- pass
- elif os.path.exists(homedir) is True:
- c.home_dir = homedir
+ elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+ else:
+ pass
+
+ try:
+ result = c.key_export(pattern=logrus)
+ except:
+ result = c.key_export(pattern=None)
+
+ if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ else:
+ pass
+#+end_src
+
+It is important to note that the result will only return =None= when a
+pattern has been entered for =logrus=, but it has not matched any
+keys. When the search pattern itself is set to =None= this triggers
+the exporting of the entire public keybox.
+
+#+begin_src python
+ import gpg
+ import os.path
+ import sys
+
+ print("""
+ This script exports one or more public keys in minimised form.
+ """)
+
+ c = gpg.Context(armor=True)
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
else:
pass
+ elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+ else:
+ pass
- try:
- result = c.key_export_minimal(pattern=logrus)
- except:
- result = c.key_export_minimal(pattern=None)
+ try:
+ result = c.key_export_minimal(pattern=logrus)
+ except:
+ result = c.key_export_minimal(pattern=None)
- if result is not None:
- with open(keyfile, "wb") as f:
- f.write(result)
- else:
- pass
- #+end_src
+ if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ else:
+ pass
+#+end_src
*** Exporting secret keys
@@ -654,162 +648,161 @@
:CUSTOM_ID: howto-export-secret-key
:END:
- Exporting secret keys is, functionally, very similar to exporting
- public keys; save for the invocation of =pinentry= via =gpg-agent=
- in order to securely enter the key's passphrase and authorise the
- export.
-
- The following example exports the secret key to a file which is
- then set with the same permissions as the output files created by
- the command line secret key export options.
-
- #+begin_src python
- import gpg
- import os
- import os.path
- import sys
-
- print("""
- This script exports one or more secret keys.
-
- The gpg-agent and pinentry are invoked to authorise the export.
- """)
-
- c = gpg.Context(armor=True)
-
- if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
- elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
- elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
- else:
- keyfile = input("Enter the path and filename to save the secret key to: ")
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
- if homedir.startswith("~"):
- if os.path.exists(os.path.expanduser(homedir)) is True:
- c.home_dir = os.path.expanduser(homedir)
- else:
- pass
- elif os.path.exists(homedir) is True:
- c.home_dir = homedir
+Exporting secret keys is, functionally, very similar to exporting
+public keys; save for the invocation of =pinentry= via =gpg-agent= in
+order to securely enter the key's passphrase and authorise the export.
+
+The following example exports the secret key to a file which is then
+set with the same permissions as the output files created by the
+command line secret key export options.
+
+#+begin_src python
+ import gpg
+ import os
+ import os.path
+ import sys
+
+ print("""
+ This script exports one or more secret keys.
+
+ The gpg-agent and pinentry are invoked to authorise the export.
+ """)
+
+ c = gpg.Context(armor=True)
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the path and filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
else:
pass
-
- try:
- result = c.key_export_secret(pattern=logrus)
- except:
- result = c.key_export_secret(pattern=None)
-
- if result is not None:
- with open(keyfile, "wb") as f:
- f.write(result)
- os.chmod(keyfile, 0o600)
+ elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+ else:
+ pass
+
+ try:
+ result = c.key_export_secret(pattern=logrus)
+ except:
+ result = c.key_export_secret(pattern=None)
+
+ if result is not None:
+ with open(keyfile, "wb") as f:
+ f.write(result)
+ os.chmod(keyfile, 0o600)
+ else:
+ pass
+#+end_src
+
+Alternatively the approach of the following script can be used. This
+longer example saves the exported secret key(s) in files in the GnuPG
+home directory, in addition to setting the file permissions as only
+readable and writable by the user. It also exports the secret key(s)
+twice in order to output both GPG binary (=.gpg=) and ASCII armoured
+(=.asc=) files.
+
+#+begin_src python
+ import gpg
+ import os
+ import os.path
+ import subprocess
+ import sys
+
+ print("""
+ This script exports one or more secret keys as both ASCII armored and binary
+ file formats, saved in files within the user's GPG home directory.
+
+ The gpg-agent and pinentry are invoked to authorise the export.
+ """)
+
+ if sys.platform == "win32":
+ gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+ else:
+ gpgconfcmd = "gpgconf --list-dirs homedir"
+
+ a = gpg.Context(armor=True)
+ b = gpg.Context()
+ c = gpg.Context()
+
+ if len(sys.argv) >= 4:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = sys.argv[3]
+ elif len(sys.argv) == 3:
+ keyfile = sys.argv[1]
+ logrus = sys.argv[2]
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ elif len(sys.argv) == 2:
+ keyfile = sys.argv[1]
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+ else:
+ keyfile = input("Enter the filename to save the secret key to: ")
+ logrus = input("Enter the UID matching the secret key(s) to export: ")
+ homedir = input("Enter the GPG configuration directory path (optional): ")
+
+ if homedir.startswith("~"):
+ if os.path.exists(os.path.expanduser(homedir)) is True:
+ c.home_dir = os.path.expanduser(homedir)
else:
pass
- #+end_src
-
- Alternatively the approach of the following script can be
- used. This longer example saves the exported secret key(s) in
- files in the GnuPG home directory, in addition to setting the file
- permissions as only readable and writable by the user. It also
- exports the secret key(s) twice in order to output both GPG binary
- (=.gpg=) and ASCII armoured (=.asc=) files.
-
- #+begin_src python
- import gpg
- import os
- import os.path
- import subprocess
- import sys
-
- print("""
- This script exports one or more secret keys as both ASCII armored and binary
- file formats, saved in files within the user's GPG home directory.
-
- The gpg-agent and pinentry are invoked to authorise the export.
- """)
-
- if sys.platform == "win32":
- gpgconfcmd = "gpgconf.exe --list-dirs homedir"
+ elif os.path.exists(homedir) is True:
+ c.home_dir = homedir
+ else:
+ pass
+
+ if c.home_dir is not None:
+ if c.home_dir.endswith("/"):
+ gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
+ ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
else:
- gpgconfcmd = "gpgconf --list-dirs homedir"
-
- a = gpg.Context(armor=True)
- b = gpg.Context()
- c = gpg.Context()
-
- if len(sys.argv) >= 4:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = sys.argv[3]
- elif len(sys.argv) == 3:
- keyfile = sys.argv[1]
- logrus = sys.argv[2]
- homedir = input("Enter the GPG configuration directory path (optional): ")
- elif len(sys.argv) == 2:
- keyfile = sys.argv[1]
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
+ gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
+ ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
+ else:
+ if os.path.exists(os.environ["GNUPGHOME"]) is True:
+ hd = os.environ["GNUPGHOME"]
else:
- keyfile = input("Enter the filename to save the secret key to: ")
- logrus = input("Enter the UID matching the secret key(s) to export: ")
- homedir = input("Enter the GPG configuration directory path (optional): ")
-
- if homedir.startswith("~"):
- if os.path.exists(os.path.expanduser(homedir)) is True:
- c.home_dir = os.path.expanduser(homedir)
- else:
- pass
- elif os.path.exists(homedir) is True:
- c.home_dir = homedir
- else:
- pass
-
- if c.home_dir is not None:
- if c.home_dir.endswith("/"):
- gpgfile = "{0}{1}.gpg".format(c.home_dir, keyfile)
- ascfile = "{0}{1}.asc".format(c.home_dir, keyfile)
- else:
- gpgfile = "{0}/{1}.gpg".format(c.home_dir, keyfile)
- ascfile = "{0}/{1}.asc".format(c.home_dir, keyfile)
- else:
- if os.path.exists(os.environ["GNUPGHOME"]) is True:
- hd = os.environ["GNUPGHOME"]
- else:
- hd = subprocess.getoutput(gpgconfcmd)
- gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
- ascfile = "{0}/{1}.asc".format(hd, keyfile)
-
- try:
- a_result = a.key_export_secret(pattern=logrus)
- b_result = b.key_export_secret(pattern=logrus)
- except:
- a_result = a.key_export_secret(pattern=None)
- b_result = b.key_export_secret(pattern=None)
-
- if a_result is not None:
- with open(ascfile, "wb") as f:
- f.write(a_result)
- os.chmod(ascfile, 0o600)
- else:
- pass
-
- if b_result is not None:
- with open(gpgfile, "wb") as f:
- f.write(b_result)
- os.chmod(gpgfile, 0o600)
- else:
- pass
- #+end_src
+ hd = subprocess.getoutput(gpgconfcmd)
+ gpgfile = "{0}/{1}.gpg".format(hd, keyfile)
+ ascfile = "{0}/{1}.asc".format(hd, keyfile)
+
+ try:
+ a_result = a.key_export_secret(pattern=logrus)
+ b_result = b.key_export_secret(pattern=logrus)
+ except:
+ a_result = a.key_export_secret(pattern=None)
+ b_result = b.key_export_secret(pattern=None)
+
+ if a_result is not None:
+ with open(ascfile, "wb") as f:
+ f.write(a_result)
+ os.chmod(ascfile, 0o600)
+ else:
+ pass
+
+ if b_result is not None:
+ with open(gpgfile, "wb") as f:
+ f.write(b_result)
+ os.chmod(gpgfile, 0o600)
+ else:
+ pass
+#+end_src
* Basic Functions
@@ -817,10 +810,10 @@
:CUSTOM_ID: howto-the-basics
:END:
- The most frequently called features of any cryptographic library
- will be the most fundamental tasks for encryption software. In this
- section we will look at how to programmatically encrypt data,
- decrypt it, sign it and verify signatures.
+The most frequently called features of any cryptographic library will
+be the most fundamental tasks for encryption software. In this
+section we will look at how to programmatically encrypt data, decrypt
+it, sign it and verify signatures.
** Encryption
@@ -828,10 +821,9 @@
:CUSTOM_ID: howto-basic-encryption
:END:
- Encrypting is very straight forward. In the first example below
- the message, =text=, is encrypted to a single recipient's key. In
- the second example the message will be encrypted to multiple
- recipients.
+Encrypting is very straight forward. In the first example below the
+message, =text=, is encrypted to a single recipient's key. In the
+second example the message will be encrypted to multiple recipients.
*** Encrypting to one key
@@ -839,74 +831,71 @@
:CUSTOM_ID: howto-basic-encryption-single
:END:
- Once the the Context is set the main issues with encrypting data
- is essentially reduced to key selection and the keyword arguments
- specified in the =gpg.Context().encrypt()= method.
-
- Those keyword arguments are: =recipients=, a list of keys
- encrypted to (covered in greater detail in the following section);
- =sign=, whether or not to sign the plaintext data, see subsequent
- sections on signing and verifying signatures below (defaults to
- =True=); =sink=, to write results or partial results to a secure
- sink instead of returning it (defaults to =None=); =passphrase=,
- only used when utilising symmetric encryption (defaults to
- =None=); =always_trust=, used to override the trust model settings
- for recipient keys (defaults to =False=); =add_encrypt_to=,
- utilises any preconfigured =encrypt-to= or =default-key= settings
- in the user's =gpg.conf= file (defaults to =False=); =prepare=,
- prepare for encryption (defaults to =False=); =expect_sign=,
- prepare for signing (defaults to =False=); =compress=, compresses
- the plaintext prior to encryption (defaults to =True=).
-
- #+begin_src python
- import gpg
-
- a_key = "0x12345678DEADBEEF"
- text = b"""Some text to test with.
-
- Since the text in this case must be bytes, it is most likely that
- the input form will be a separate file which is opened with "rb"
- as this is the simplest method of obtaining the correct data
- format.
- """
-
- c = gpg.Context(armor=True)
- rkey = list(c.keylist(pattern=a_key, secret=False))
- ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
-
- with open("secret_plans.txt.asc", "wb") as afile:
+Once the the Context is set the main issues with encrypting data is
+essentially reduced to key selection and the keyword arguments
+specified in the =gpg.Context().encrypt()= method.
+
+Those keyword arguments are: =recipients=, a list of keys encrypted to
+(covered in greater detail in the following section); =sign=, whether
+or not to sign the plaintext data, see subsequent sections on signing
+and verifying signatures below (defaults to =True=); =sink=, to write
+results or partial results to a secure sink instead of returning it
+(defaults to =None=); =passphrase=, only used when utilising symmetric
+encryption (defaults to =None=); =always_trust=, used to override the
+trust model settings for recipient keys (defaults to =False=);
+=add_encrypt_to=, utilises any preconfigured =encrypt-to= or
+=default-key= settings in the user's =gpg.conf= file (defaults to
+=False=); =prepare=, prepare for encryption (defaults to =False=);
+=expect_sign=, prepare for signing (defaults to =False=); =compress=,
+compresses the plaintext prior to encryption (defaults to =True=).
+
+#+begin_src python
+ import gpg
+
+ a_key = "0x12345678DEADBEEF"
+ text = b"""Some text to test with.
+
+ Since the text in this case must be bytes, it is most likely that
+ the input form will be a separate file which is opened with "rb"
+ as this is the simplest method of obtaining the correct data
+ format.
+ """
+
+ c = gpg.Context(armor=True)
+ rkey = list(c.keylist(pattern=a_key, secret=False))
+ ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=False)
+
+ with open("secret_plans.txt.asc", "wb") as afile:
afile.write(ciphertext)
- #+end_src
+#+end_src
- Though this is even more likely to be used like this; with the
- plaintext input read from a file, the recipient keys used for
- encryption regardless of key trust status and the encrypted output
- also encrypted to any preconfigured keys set in the =gpg.conf=
- file:
+Though this is even more likely to be used like this; with the
+plaintext input read from a file, the recipient keys used for
+encryption regardless of key trust status and the encrypted output
+also encrypted to any preconfigured keys set in the =gpg.conf= file:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- a_key = "0x12345678DEADBEEF"
+ a_key = "0x12345678DEADBEEF"
- with open("secret_plans.txt", "rb") as afile:
+ with open("secret_plans.txt", "rb") as afile:
text = afile.read()
- c = gpg.Context(armor=True)
- rkey = list(c.keylist(pattern=a_key, secret=False))
- ciphertext, result, sign_result = c.encrypt(text, recipients=rkey,
- sign=True, always_trust=True,
- add_encrypt_to=True)
+ c = gpg.Context(armor=True)
+ rkey = list(c.keylist(pattern=a_key, secret=False))
+ ciphertext, result, sign_result = c.encrypt(text, recipients=rkey, sign=True,
+ always_trust=True,
+ add_encrypt_to=True)
- with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
- #+end_src
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+#+end_src
- If the =recipients= paramater is empty then the plaintext is
- encrypted symmetrically. If no =passphrase= is supplied as a
- parameter or via a callback registered with the =Context()= then
- an out-of-band prompt for the passphrase via pinentry will be
- invoked.
+If the =recipients= paramater is empty then the plaintext is encrypted
+symmetrically. If no =passphrase= is supplied as a parameter or via a
+callback registered with the =Context()= then an out-of-band prompt
+for the passphrase via pinentry will be invoked.
*** Encrypting to multiple keys
@@ -914,101 +903,101 @@
:CUSTOM_ID: howto-basic-encryption-multiple
:END:
- Encrypting to multiple keys essentially just expands upon the key
- selection process and the recipients from the previous examples.
+Encrypting to multiple keys essentially just expands upon the key
+selection process and the recipients from the previous examples.
- The following example encrypts a message (=text=) to everyone with
- an email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt
- to a default key or other key which is configured to normally
- encrypt to.
+The following example encrypts a message (=text=) to everyone with an
+email address on the =gnupg.org= domain,[fn:3] but does /not/ encrypt
+to a default key or other key which is configured to normally encrypt
+to.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- text = b"""Oh look, another test message.
+ text = b"""Oh look, another test message.
- The same rules apply as with the previous example and more likely
- than not, the message will actually be drawn from reading the
- contents of a file or, maybe, from entering data at an input()
- prompt.
+ The same rules apply as with the previous example and more likely
+ than not, the message will actually be drawn from reading the
+ contents of a file or, maybe, from entering data at an input()
+ prompt.
- Since the text in this case must be bytes, it is most likely that
- the input form will be a separate file which is opened with "rb"
- as this is the simplest method of obtaining the correct data
- format.
- """
+ Since the text in this case must be bytes, it is most likely that
+ the input form will be a separate file which is opened with "rb"
+ as this is the simplest method of obtaining the correct data
+ format.
+ """
- c = gpg.Context(armor=True)
- rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
- logrus = []
+ c = gpg.Context(armor=True)
+ rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+ logrus = []
- for i in range(len(rpattern)):
- if rpattern[i].can_encrypt == 1:
- logrus.append(rpattern[i])
+ for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
- ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
- sign=False, always_trust=True)
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ sign=False, always_trust=True)
- with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
- #+end_src
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+#+end_src
- All it would take to change the above example to sign the message
- and also encrypt the message to any configured default keys would
- be to change the =c.encrypt= line to this:
+All it would take to change the above example to sign the message
+and also encrypt the message to any configured default keys would
+be to change the =c.encrypt= line to this:
- #+begin_src python
- ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
- always_trust=True,
- add_encrypt_to=True)
- #+end_src
+#+begin_src python
+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
+ always_trust=True,
+ add_encrypt_to=True)
+#+end_src
- The only keyword arguments requiring modification are those for
- which the default values are changing. The default value of
- =sign= is =True=, the default of =always_trust= is =False=, the
- default of =add_encrypt_to= is =False=.
+The only keyword arguments requiring modification are those for which
+the default values are changing. The default value of =sign= is
+=True=, the default of =always_trust= is =False=, the default of
+=add_encrypt_to= is =False=.
- If =always_trust= is not set to =True= and any of the recipient
- keys are not trusted (e.g. not signed or locally signed) then the
- encryption will raise an error. It is possible to mitigate this
- somewhat with something more like this:
+If =always_trust= is not set to =True= and any of the recipient keys
+are not trusted (e.g. not signed or locally signed) then the
+encryption will raise an error. It is possible to mitigate this
+somewhat with something more like this:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- with open("secret_plans.txt.asc", "rb") as afile:
+ with open("secret_plans.txt.asc", "rb") as afile:
text = afile.read()
- c = gpg.Context(armor=True)
- rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
- logrus = []
+ c = gpg.Context(armor=True)
+ rpattern = list(c.keylist(pattern="@gnupg.org", secret=False))
+ logrus = []
- for i in range(len(rpattern)):
- if rpattern[i].can_encrypt == 1:
- logrus.append(rpattern[i])
-
- try:
- ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
- add_encrypt_to=True)
- except gpg.errors.InvalidRecipients as e:
- for i in range(len(e.recipients)):
- for n in range(len(logrus)):
- if logrus[n].fpr == e.recipients[i].fpr:
- logrus.remove(logrus[n])
- else:
- pass
+ for i in range(len(rpattern)):
+ if rpattern[i].can_encrypt == 1:
+ logrus.append(rpattern[i])
try:
ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
add_encrypt_to=True)
- with open("secret_plans.txt.asc", "wb") as afile:
- afile.write(ciphertext)
- except:
- pass
- #+end_src
+ except gpg.errors.InvalidRecipients as e:
+ for i in range(len(e.recipients)):
+ for n in range(len(logrus)):
+ if logrus[n].fpr == e.recipients[i].fpr:
+ logrus.remove(logrus[n])
+ else:
+ pass
+ try:
+ ciphertext, result, sign_result = c.encrypt(text,
+ recipients=logrus,
+ add_encrypt_to=True)
+ with open("secret_plans.txt.asc", "wb") as afile:
+ afile.write(ciphertext)
+ except:
+ pass
+#+end_src
- This will attempt to encrypt to all the keys searched for, then
- remove invalid recipients if it fails and try again.
+This will attempt to encrypt to all the keys searched for, then remove
+invalid recipients if it fails and try again.
** Decryption
@@ -1016,39 +1005,39 @@
:CUSTOM_ID: howto-basic-decryption
:END:
- Decrypting something encrypted to a key in one's secret keyring is
- fairly straight forward.
+Decrypting something encrypted to a key in one's secret keyring is
+fairly straight forward.
- In this example code, however, preconfiguring either
- =gpg.Context()= or =gpg.core.Context()= as =c= is unnecessary
- because there is no need to modify the Context prior to conducting
- the decryption and since the Context is only used once, setting it
- to =c= simply adds lines for no gain.
+In this example code, however, preconfiguring either =gpg.Context()=
+or =gpg.core.Context()= as =c= is unnecessary because there is no need
+to modify the Context prior to conducting the decryption and since the
+Context is only used once, setting it to =c= simply adds lines for no
+gain.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- ciphertext = input("Enter path and filename of encrypted file: ")
- newfile = input("Enter path and filename of file to save decrypted data to: ")
+ ciphertext = input("Enter path and filename of encrypted file: ")
+ newfile = input("Enter path and filename of file to save decrypted data to: ")
- with open(ciphertext, "rb") as cfile:
- try:
- plaintext, result, verify_result = gpg.Context().decrypt(cfile)
- except gpg.errors.GPGMEError as e:
- plaintext = None
- print(e)
+ with open(ciphertext, "rb") as cfile:
+ try:
+ plaintext, result, verify_result = gpg.Context().decrypt(cfile)
+ except gpg.errors.GPGMEError as e:
+ plaintext = None
+ print(e)
- if plaintext is not None:
- with open(newfile, "wb") as nfile:
- nfile.write(plaintext)
- else:
- pass
- #+end_src
+ if plaintext is not None:
+ with open(newfile, "wb") as nfile:
+ nfile.write(plaintext)
+ else:
+ pass
+#+end_src
- The data available in =plaintext= in this example is the decrypted
- content as a byte object, the recipient key IDs and algorithms in
- =result= and the results of verifying any signatures of the data in
- =verify_result=.
+The data available in =plaintext= in this example is the decrypted
+content as a byte object, the recipient key IDs and algorithms in
+=result= and the results of verifying any signatures of the data in
+=verify_result=.
** Signing text and files
@@ -1056,7 +1045,7 @@
:CUSTOM_ID: howto-basic-signing
:END:
- The following sections demonstrate how to specify keys to sign with.
+The following sections demonstrate how to specify keys to sign with.
*** Signing key selection
@@ -1064,30 +1053,30 @@
:CUSTOM_ID: howto-basic-signing-signers
:END:
- By default GPGME and the Python bindings will use the default key
- configured for the user invoking the GPGME API. If there is no
- default key specified and there is more than one secret key
- available it may be necessary to specify the key or keys with
- which to sign messages and files.
+By default GPGME and the Python bindings will use the default key
+configured for the user invoking the GPGME API. If there is no
+default key specified and there is more than one secret key available
+it may be necessary to specify the key or keys with which to sign
+messages and files.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- logrus = input("Enter the email address or string to match signing keys to: ")
- hancock = gpg.Context().keylist(pattern=logrus, secret=True)
- sig_src = list(hancock)
- #+end_src
+ logrus = input("Enter the email address or string to match signing keys to: ")
+ hancock = gpg.Context().keylist(pattern=logrus, secret=True)
+ sig_src = list(hancock)
+#+end_src
- The signing examples in the following sections include the
- explicitly designated =signers= parameter in two of the five
- examples; once where the resulting signature would be ASCII
- armoured and once where it would not be armoured.
+The signing examples in the following sections include the explicitly
+designated =signers= parameter in two of the five examples; once where
+the resulting signature would be ASCII armoured and once where it
+would not be armoured.
- While it would be possible to enter a key ID or fingerprint here
- to match a specific key, it is not possible to enter two
- fingerprints and match two keys since the patten expects a string,
- bytes or None and not a list. A string with two fingerprints
- won't match any single key.
+While it would be possible to enter a key ID or fingerprint here to
+match a specific key, it is not possible to enter two fingerprints and
+match two keys since the patten expects a string, bytes or None and
+not a list. A string with two fingerprints won't match any single
+key.
*** Normal or default signing messages or files
@@ -1095,54 +1084,54 @@
:CUSTOM_ID: howto-basic-signing-normal
:END:
- The normal or default signing process is essentially the same as
- is most often invoked when also encrypting a message or file. So
- when the encryption component is not utilised, the result is to
- produce an encoded and signed output which may or may not be ASCII
- armoured and which may or may not also be compressed.
+The normal or default signing process is essentially the same as is
+most often invoked when also encrypting a message or file. So when
+the encryption component is not utilised, the result is to produce an
+encoded and signed output which may or may not be ASCII armoured and
+which may or may not also be compressed.
- By default compression will be used unless GnuPG detects that the
- plaintext is already compressed. ASCII armouring will be
- determined according to the value of =gpg.Context().armor=.
+By default compression will be used unless GnuPG detects that the
+plaintext is already compressed. ASCII armouring will be determined
+according to the value of =gpg.Context().armor=.
- The compression algorithm is selected in much the same way as the
- symmetric encryption algorithm or the hash digest algorithm is
- when multiple keys are involved; from the preferences saved into
- the key itself or by comparison with the preferences with all
- other keys involved.
+The compression algorithm is selected in much the same way as the
+symmetric encryption algorithm or the hash digest algorithm is when
+multiple keys are involved; from the preferences saved into the key
+itself or by comparison with the preferences with all other keys
+involved.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- text0 = """Declaration of ... something.
+ text0 = """Declaration of ... something.
- """
- text = text0.encode()
+ """
+ text = text0.encode()
- c = gpg.Context(armor=True, signers=sig_src)
- signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+ c = gpg.Context(armor=True, signers=sig_src)
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
- with open("/path/to/statement.txt.asc", "w") as afile:
- afile.write(signed_data.decode())
- #+end_src
+ with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+#+end_src
- Though everything in this example is accurate, it is more likely
- that reading the input data from another file and writing the
- result to a new file will be performed more like the way it is done
- in the next example. Even if the output format is ASCII armoured.
+Though everything in this example is accurate, it is more likely that
+reading the input data from another file and writing the result to a
+new file will be performed more like the way it is done in the next
+example. Even if the output format is ASCII armoured.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- with open("/path/to/statement.txt", "rb") as tfile:
- text = tfile.read()
+ with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
- c = gpg.Context()
- signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
+ c = gpg.Context()
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
- with open("/path/to/statement.txt.sig", "wb") as afile:
- afile.write(signed_data)
- #+end_src
+ with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+#+end_src
*** Detached signing messages and files
@@ -1150,41 +1139,40 @@
:CUSTOM_ID: howto-basic-signing-detached
:END:
- Detached signatures will often be needed in programmatic uses of
- GPGME, either for signing files (e.g. tarballs of code releases)
- or as a component of message signing (e.g. PGP/MIME encoded
- email).
+Detached signatures will often be needed in programmatic uses of
+GPGME, either for signing files (e.g. tarballs of code releases) or as
+a component of message signing (e.g. PGP/MIME encoded email).
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- text0 = """Declaration of ... something.
+ text0 = """Declaration of ... something.
- """
- text = text0.encode()
+ """
+ text = text0.encode()
- c = gpg.Context(armor=True)
- signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+ c = gpg.Context(armor=True)
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
- with open("/path/to/statement.txt.asc", "w") as afile:
- afile.write(signed_data.decode())
- #+end_src
+ with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+#+end_src
- As with normal signatures, detached signatures are best handled as
- byte literals, even when the output is ASCII armoured.
+As with normal signatures, detached signatures are best handled as
+byte literals, even when the output is ASCII armoured.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- with open("/path/to/statement.txt", "rb") as tfile:
- text = tfile.read()
+ with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
- c = gpg.Context(signers=sig_src)
- signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
+ c = gpg.Context(signers=sig_src)
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
- with open("/path/to/statement.txt.sig", "wb") as afile:
- afile.write(signed_data)
- #+end_src
+ with open("/path/to/statement.txt.sig", "wb") as afile:
+ afile.write(signed_data)
+#+end_src
*** Clearsigning messages or text
@@ -1192,41 +1180,40 @@
:CUSTOM_ID: howto-basic-signing-clear
:END:
- Though PGP/in-line messages are no longer encouraged in favour of
- PGP/MIME, there is still sometimes value in utilising in-line
- signatures. This is where clear-signed messages or text is of
- value.
+Though PGP/in-line messages are no longer encouraged in favour of
+PGP/MIME, there is still sometimes value in utilising in-line
+signatures. This is where clear-signed messages or text is of value.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- text0 = """Declaration of ... something.
+ text0 = """Declaration of ... something.
- """
- text = text0.encode()
+ """
+ text = text0.encode()
- c = gpg.Context()
- signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+ c = gpg.Context()
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
- with open("/path/to/statement.txt.asc", "w") as afile:
- afile.write(signed_data.decode())
- #+end_src
+ with open("/path/to/statement.txt.asc", "w") as afile:
+ afile.write(signed_data.decode())
+#+end_src
- In spite of the appearance of a clear-signed message, the data
- handled by GPGME in signing it must still be byte literals.
+In spite of the appearance of a clear-signed message, the data handled
+by GPGME in signing it must still be byte literals.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- with open("/path/to/statement.txt", "rb") as tfile:
- text = tfile.read()
+ with open("/path/to/statement.txt", "rb") as tfile:
+ text = tfile.read()
- c = gpg.Context()
- signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
+ c = gpg.Context()
+ signed_data, result = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
- with open("/path/to/statement.txt.asc", "wb") as afile:
- afile.write(signed_data)
- #+end_src
+ with open("/path/to/statement.txt.asc", "wb") as afile:
+ afile.write(signed_data)
+#+end_src
** Signature verification
@@ -1234,152 +1221,152 @@
:CUSTOM_ID: howto-basic-verification
:END:
- Essentially there are two principal methods of verification of a
- signature. The first of these is for use with the normal or
- default signing method and for clear-signed messages. The second is
- for use with files and data with detached signatures.
-
- The following example is intended for use with the default signing
- method where the file was not ASCII armoured:
-
- #+begin_src python
- import gpg
- import time
-
- filename = "statement.txt"
- gpg_file = "statement.txt.gpg"
-
- c = gpg.Context()
-
- try:
- data, result = c.verify(open(gpg_file))
- verified = True
- except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
- if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
- {0}
- with key {1}
- made at {2}
- """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
- else:
- pass
- #+end_src
-
- Whereas this next example, which is almost identical would work
- with normal ASCII armoured files and with clear-signed files:
-
- #+begin_src python
- import gpg
- import time
-
- filename = "statement.txt"
- asc_file = "statement.txt.asc"
-
- c = gpg.Context()
-
- try:
- data, result = c.verify(open(asc_file))
- verified = True
- except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
- if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
- {0}
- with key {1}
- made at {2}
- """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
- else:
- pass
- #+end_src
-
- In both of the previous examples it is also possible to compare the
- original data that was signed against the signed data in =data= to
- see if it matches with something like this:
-
- #+begin_src python
- with open(filename, "rb") as afile:
- text = afile.read()
-
- if text == data:
- print("Good signature.")
- else:
- pass
- #+end_src
-
- The following two examples, however, deal with detached signatures.
- With his method of verification the data that was signed does not
- get returned since it is already being explicitly referenced in the
- first argument of =c.verify=. So =data= is =None= and only the
- information in =result= is available.
-
- #+begin_src python
- import gpg
- import time
-
- filename = "statement.txt"
- sig_file = "statement.txt.sig"
-
- c = gpg.Context()
-
- try:
- data, result = c.verify(open(filename), open(sig_file))
- verified = True
- except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
- if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
- {0}
- with key {1}
- made at {2}
- """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
- else:
- pass
- #+end_src
-
- #+begin_src python
- import gpg
- import time
-
- filename = "statement.txt"
- asc_file = "statement.txt.asc"
-
- c = gpg.Context()
-
- try:
- data, result = c.verify(open(filename), open(asc_file))
- verified = True
- except gpg.errors.BadSignatures as e:
- verified = False
- print(e)
-
- if verified is True:
- for i in range(len(result.signatures)):
- sign = result.signatures[i]
- print("""Good signature from:
- {0}
- with key {1}
- made at {2}
- """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
- time.ctime(sign.timestamp)))
- else:
- pass
- #+end_src
+Essentially there are two principal methods of verification of a
+signature. The first of these is for use with the normal or default
+signing method and for clear-signed messages. The second is for use
+with files and data with detached signatures.
+
+The following example is intended for use with the default signing
+method where the file was not ASCII armoured:
+
+#+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ gpg_file = "statement.txt.gpg"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(gpg_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+#+end_src
+
+Whereas this next example, which is almost identical would work with
+normal ASCII armoured files and with clear-signed files:
+
+#+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ asc_file = "statement.txt.asc"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(asc_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+#+end_src
+
+In both of the previous examples it is also possible to compare the
+original data that was signed against the signed data in =data= to see
+if it matches with something like this:
+
+#+begin_src python
+ with open(filename, "rb") as afile:
+ text = afile.read()
+
+ if text == data:
+ print("Good signature.")
+ else:
+ pass
+#+end_src
+
+The following two examples, however, deal with detached signatures.
+With his method of verification the data that was signed does not get
+returned since it is already being explicitly referenced in the first
+argument of =c.verify=. So =data= is =None= and only the information
+in =result= is available.
+
+#+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ sig_file = "statement.txt.sig"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(filename), open(sig_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+#+end_src
+
+#+begin_src python
+ import gpg
+ import time
+
+ filename = "statement.txt"
+ asc_file = "statement.txt.asc"
+
+ c = gpg.Context()
+
+ try:
+ data, result = c.verify(open(filename), open(asc_file))
+ verified = True
+ except gpg.errors.BadSignatures as e:
+ verified = False
+ print(e)
+
+ if verified is True:
+ for i in range(len(result.signatures)):
+ sign = result.signatures[i]
+ print("""Good signature from:
+ {0}
+ with key {1}
+ made at {2}
+ """.format(c.get_key(sign.fpr).uids[0].uid, sign.fpr,
+ time.ctime(sign.timestamp)))
+ else:
+ pass
+#+end_src
* Creating keys and subkeys
@@ -1387,34 +1374,33 @@
:CUSTOM_ID: key-generation
:END:
- The one thing, aside from GnuPG itself, that GPGME depends on, of
- course, is the keys themselves. So it is necessary to be able to
- generate them and modify them by adding subkeys, revoking or
- disabling them, sometimes deleting them and doing the same for user
- IDs.
-
- In the following examples a key will be created for the world's
- greatest secret agent, Danger Mouse. Since Danger Mouse is a secret
- agent he needs to be able to protect information to =SECRET= level
- clearance, so his keys will be 3072-bit keys.
-
- The pre-configured =gpg.conf= file which sets cipher, digest and
- other preferences contains the following configuration parameters:
-
- #+begin_src conf
- expert
- allow-freeform-uid
- allow-secret-key-import
- trust-model tofu+pgp
- tofu-default-policy unknown
- enable-large-rsa
- enable-dsa2
- cert-digest-algo SHA512
- default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
- personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
- personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
- personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
- #+end_src
+The one thing, aside from GnuPG itself, that GPGME depends on, of
+course, is the keys themselves. So it is necessary to be able to
+generate them and modify them by adding subkeys, revoking or disabling
+them, sometimes deleting them and doing the same for user IDs.
+
+In the following examples a key will be created for the world's
+greatest secret agent, Danger Mouse. Since Danger Mouse is a secret
+agent he needs to be able to protect information to =SECRET= level
+clearance, so his keys will be 3072-bit keys.
+
+The pre-configured =gpg.conf= file which sets cipher, digest and other
+preferences contains the following configuration parameters:
+
+#+begin_src conf
+ expert
+ allow-freeform-uid
+ allow-secret-key-import
+ trust-model tofu+pgp
+ tofu-default-policy unknown
+ enable-large-rsa
+ enable-dsa2
+ cert-digest-algo SHA512
+ default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed
+ personal-cipher-preferences TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES
+ personal-digest-preferences SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1
+ personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+#+end_src
** Primary key
@@ -1422,100 +1408,98 @@
:CUSTOM_ID: keygen-primary
:END:
- Generating a primary key uses the =create_key= method in a Context.
- It contains multiple arguments and keyword arguments, including:
- =userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=,
- =certify=, =authenticate=, =passphrase= and =force=. The defaults
- for all of those except =userid=, =algorithm=, =expires_in=,
- =expires= and =passphrase= is =False=. The defaults for
- =algorithm= and =passphrase= is =None=. The default for
- =expires_in= is =0=. The default for =expires= is =True=. There
- is no default for =userid=.
-
- If =passphrase= is left as =None= then the key will not be
- generated with a passphrase, if =passphrase= is set to a string
- then that will be the passphrase and if =passphrase= is set to
- =True= then gpg-agent will launch pinentry to prompt for a
- passphrase. For the sake of convenience, these examples will keep
- =passphrase= set to =None=.
-
- #+begin_src python
- import gpg
-
- c = gpg.Context()
-
- c.home_dir = "~/.gnupg-dm"
- userid = "Danger Mouse <[email protected]>"
-
- dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
- sign=True, certify=True)
- #+end_src
-
- One thing to note here is the use of setting the =c.home_dir=
- parameter. This enables generating the key or keys in a different
- location. In this case to keep the new key data created for this
- example in a separate location rather than adding it to existing
- and active key store data. As with the default directory,
- =~/.gnupg=, any temporary or separate directory needs the
- permissions set to only permit access by the directory owner. On
- posix systems this means setting the directory permissions to 700.
-
- The =temp-homedir-config.py= script in the HOWTO examples directory
- will create an alternative homedir with these configuration options
- already set and the correct directory and file permissions.
-
- The successful generation of the key can be confirmed via the
- returned =GenkeyResult= object, which includes the following data:
-
- #+begin_src python
- print("""
- Fingerprint: {0}
- Primary Key: {1}
- Public Key: {2}
- Secret Key: {3}
- Sub Key: {4}
- User IDs: {5}
- """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
- dmkey.uid))
- #+end_src
-
- Alternatively the information can be confirmed using the command
- line program:
-
- #+begin_src shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm -K
- ~/.gnupg-dm/pubring.kbx
- ----------------------
- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
- 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
- uid [ultimate] Danger Mouse <[email protected]>
-
- bash-4.4$
- #+end_src
-
- As with generating keys manually, to preconfigure expanded
- preferences for the cipher, digest and compression algorithms, the
- =gpg.conf= file must contain those details in the home directory in
- which the new key is being generated. I used a cut down version of
- my own =gpg.conf= file in order to be able to generate this:
-
- #+begin_src shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
- Secret key is available.
-
- sec rsa3072/026D2F19E99E63AA
- created: 2018-03-15 expires: 2019-03-15 usage: SC
- trust: ultimate validity: ultimate
- [ultimate] (1). Danger Mouse <[email protected]>
-
- [ultimate] (1). Danger Mouse <[email protected]>
- Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
- Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
- Compression: ZLIB, BZIP2, ZIP, Uncompressed
- Features: MDC, Keyserver no-modify
-
- bash-4.4$
- #+end_src
+Generating a primary key uses the =create_key= method in a Context.
+It contains multiple arguments and keyword arguments, including:
+=userid=, =algorithm=, =expires_in=, =expires=, =sign=, =encrypt=,
+=certify=, =authenticate=, =passphrase= and =force=. The defaults for
+all of those except =userid=, =algorithm=, =expires_in=, =expires= and
+=passphrase= is =False=. The defaults for =algorithm= and
+=passphrase= is =None=. The default for =expires_in= is =0=. The
+default for =expires= is =True=. There is no default for =userid=.
+
+If =passphrase= is left as =None= then the key will not be generated
+with a passphrase, if =passphrase= is set to a string then that will
+be the passphrase and if =passphrase= is set to =True= then gpg-agent
+will launch pinentry to prompt for a passphrase. For the sake of
+convenience, these examples will keep =passphrase= set to =None=.
+
+#+begin_src python
+ import gpg
+
+ c = gpg.Context()
+
+ c.home_dir = "~/.gnupg-dm"
+ userid = "Danger Mouse <[email protected]>"
+
+ dmkey = c.create_key(userid, algorithm="rsa3072", expires_in=31536000,
+ sign=True, certify=True)
+#+end_src
+
+One thing to note here is the use of setting the =c.home_dir=
+parameter. This enables generating the key or keys in a different
+location. In this case to keep the new key data created for this
+example in a separate location rather than adding it to existing and
+active key store data. As with the default directory, =~/.gnupg=, any
+temporary or separate directory needs the permissions set to only
+permit access by the directory owner. On posix systems this means
+setting the directory permissions to 700.
+
+The =temp-homedir-config.py= script in the HOWTO examples directory
+will create an alternative homedir with these configuration options
+already set and the correct directory and file permissions.
+
+The successful generation of the key can be confirmed via the returned
+=GenkeyResult= object, which includes the following data:
+
+#+begin_src python
+ print("""
+ Fingerprint: {0}
+ Primary Key: {1}
+ Public Key: {2}
+ Secret Key: {3}
+ Sub Key: {4}
+ User IDs: {5}
+ """.format(dmkey.fpr, dmkey.primary, dmkey.pubkey, dmkey.seckey, dmkey.sub,
+ dmkey.uid))
+#+end_src
+
+Alternatively the information can be confirmed using the command line
+program:
+
+#+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse <[email protected]>
+
+ bash-4.4$
+#+end_src
+
+As with generating keys manually, to preconfigure expanded preferences
+for the cipher, digest and compression algorithms, the =gpg.conf= file
+must contain those details in the home directory in which the new key
+is being generated. I used a cut down version of my own =gpg.conf=
+file in order to be able to generate this:
+
+#+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm --edit-key 177B7C25DB99745EE2EE13ED026D2F19E99E63AA showpref quit
+ Secret key is available.
+
+ sec rsa3072/026D2F19E99E63AA
+ created: 2018-03-15 expires: 2019-03-15 usage: SC
+ trust: ultimate validity: ultimate
+ [ultimate] (1). Danger Mouse <[email protected]>
+
+ [ultimate] (1). Danger Mouse <[email protected]>
+ Cipher: TWOFISH, CAMELLIA256, AES256, CAMELLIA192, AES192, CAMELLIA128, AES, BLOWFISH, IDEA, CAST5, 3DES
+ Digest: SHA512, SHA384, SHA256, SHA224, RIPEMD160, SHA1
+ Compression: ZLIB, BZIP2, ZIP, Uncompressed
+ Features: MDC, Keyserver no-modify
+
+ bash-4.4$
+#+end_src
** Subkeys
@@ -1523,55 +1507,55 @@
:CUSTOM_ID: keygen-subkeys
:END:
- Adding subkeys to a primary key is fairly similar to creating the
- primary key with the =create_subkey= method. Most of the arguments
- are the same, but not quite all. Instead of the =userid= argument
- there is now a =key= argument for selecting which primary key to
- add the subkey to.
-
- In the following example an encryption subkey will be added to the
- primary key. Since Danger Mouse is a security conscious secret
- agent, this subkey will only be valid for about six months, half
- the length of the primary key.
-
- #+begin_src python
- import gpg
-
- c = gpg.Context()
- c.home_dir = "~/.gnupg-dm"
-
- key = c.get_key(dmkey.fpr, secret=True)
- dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
- encrypt=True)
- #+end_src
-
- As with the primary key, the results here can be checked with:
-
- #+begin_src python
- print("""
- Fingerprint: {0}
- Primary Key: {1}
- Public Key: {2}
- Secret Key: {3}
- Sub Key: {4}
- User IDs: {5}
- """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
- dmsub.uid))
- #+end_src
-
- As well as on the command line with:
-
- #+begin_src shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm -K
- ~/.gnupg-dm/pubring.kbx
- ----------------------
- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
- 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
- uid [ultimate] Danger Mouse <[email protected]>
- ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
-
- bash-4.4$
- #+end_src
+Adding subkeys to a primary key is fairly similar to creating the
+primary key with the =create_subkey= method. Most of the arguments
+are the same, but not quite all. Instead of the =userid= argument
+there is now a =key= argument for selecting which primary key to add
+the subkey to.
+
+In the following example an encryption subkey will be added to the
+primary key. Since Danger Mouse is a security conscious secret agent,
+this subkey will only be valid for about six months, half the length
+of the primary key.
+
+#+begin_src python
+ import gpg
+
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
+
+ key = c.get_key(dmkey.fpr, secret=True)
+ dmsub = c.create_subkey(key, algorithm="rsa3072", expires_in=15768000,
+ encrypt=True)
+#+end_src
+
+As with the primary key, the results here can be checked with:
+
+#+begin_src python
+ print("""
+ Fingerprint: {0}
+ Primary Key: {1}
+ Public Key: {2}
+ Secret Key: {3}
+ Sub Key: {4}
+ User IDs: {5}
+ """.format(dmsub.fpr, dmsub.primary, dmsub.pubkey, dmsub.seckey, dmsub.sub,
+ dmsub.uid))
+#+end_src
+
+As well as on the command line with:
+
+#+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse <[email protected]>
+ ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+
+ bash-4.4$
+#+end_src
** User IDs
@@ -1585,38 +1569,38 @@
:CUSTOM_ID: keygen-uids-add
:END:
- By comparison to creating primary keys and subkeys, adding a new
- user ID to an existing key is much simpler. The method used to do
- this is =key_add_uid= and the only arguments it takes are for the
- =key= and the new =uid=.
+By comparison to creating primary keys and subkeys, adding a new user
+ID to an existing key is much simpler. The method used to do this is
+=key_add_uid= and the only arguments it takes are for the =key= and
+the new =uid=.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- c = gpg.Context()
- c.home_dir = "~/.gnupg-dm"
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
- dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
- key = c.get_key(dmfpr, secret=True)
- uid = "Danger Mouse <[email protected]>"
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret=True)
+ uid = "Danger Mouse <[email protected]>"
- c.key_add_uid(key, uid)
- #+end_src
+ c.key_add_uid(key, uid)
+#+end_src
- Unsurprisingly the result of this is:
+Unsurprisingly the result of this is:
- #+begin_src shell
- bash-4.4$ gpg --homedir ~/.gnupg-dm -K
- ~/.gnupg-dm/pubring.kbx
- ----------------------
- sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
- 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
- uid [ultimate] Danger Mouse <[email protected]>
- uid [ultimate] Danger Mouse <[email protected]>
- ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
+#+begin_src shell
+ bash-4.4$ gpg --homedir ~/.gnupg-dm -K
+ ~/.gnupg-dm/pubring.kbx
+ ----------------------
+ sec rsa3072 2018-03-15 [SC] [expires: 2019-03-15]
+ 177B7C25DB99745EE2EE13ED026D2F19E99E63AA
+ uid [ultimate] Danger Mouse <[email protected]>
+ uid [ultimate] Danger Mouse <[email protected]>
+ ssb rsa3072 2018-03-15 [E] [expires: 2018-09-13]
- bash-4.4$
- #+end_src
+ bash-4.4$
+#+end_src
*** Revokinging User IDs
@@ -1624,21 +1608,21 @@
:CUSTOM_ID: keygen-uids-revoke
:END:
- Revoking a user ID is a fairly similar process, except that it
- uses the =key_revoke_uid= method.
+Revoking a user ID is a fairly similar process, except that it uses
+the =key_revoke_uid= method.
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- c = gpg.Context()
- c.home_dir = "~/.gnupg-dm"
+ c = gpg.Context()
+ c.home_dir = "~/.gnupg-dm"
- dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
- key = c.get_key(dmfpr, secret=True)
- uid = "Danger Mouse <[email protected]>"
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret=True)
+ uid = "Danger Mouse <[email protected]>"
- c.key_revoke_uid(key, uid)
- #+end_src
+ c.key_revoke_uid(key, uid)
+#+end_src
** Key certification
@@ -1646,37 +1630,37 @@
:CUSTOM_ID: key-sign
:END:
- Since key certification is more frequently referred to as key
- signing, the method used to perform this function is =key_sign=.
+Since key certification is more frequently referred to as key signing,
+the method used to perform this function is =key_sign=.
- The =key_sign= method takes four arguments: =key=, =uids=,
- =expires_in= and =local=. The default value of =uids= is =None=
- and which results in all user IDs being selected. The default
- value of both =expires_in= and =local= is =False=; which results in
- the signature never expiring and being able to be exported.
+The =key_sign= method takes four arguments: =key=, =uids=,
+=expires_in= and =local=. The default value of =uids= is =None= and
+which results in all user IDs being selected. The default value of
+both =expires_in= and =local= is =False=; which results in the
+signature never expiring and being able to be exported.
- The =key= is the key being signed rather than the key doing the
- signing. To change the key doing the signing refer to the signing
- key selection above for signing messages and files.
+The =key= is the key being signed rather than the key doing the
+signing. To change the key doing the signing refer to the signing key
+selection above for signing messages and files.
- If the =uids= value is not =None= then it must either be a string
- to match a single user ID or a list of strings to match multiple
- user IDs. In this case the matching of those strings must be
- precise and it is case sensitive.
+If the =uids= value is not =None= then it must either be a string to
+match a single user ID or a list of strings to match multiple user
+IDs. In this case the matching of those strings must be precise and
+it is case sensitive.
- To sign Danger Mouse's key for just the initial user ID with a
- signature which will last a little over a month, do this:
+To sign Danger Mouse's key for just the initial user ID with a
+signature which will last a little over a month, do this:
- #+begin_src python
- import gpg
+#+begin_src python
+ import gpg
- c = gpg.Context()
- uid = "Danger Mouse <[email protected]>"
+ c = gpg.Context()
+ uid = "Danger Mouse <[email protected]>"
- dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
- key = c.get_key(dmfpr, secret=True)
- c.key_sign(key, uids=uid, expires_in=2764800)
- #+end_src
+ dmfpr = "177B7C25DB99745EE2EE13ED026D2F19E99E63AA"
+ key = c.get_key(dmfpr, secret=True)
+ c.key_sign(key, uids=uid, expires_in=2764800)
+#+end_src
* Miscellaneous work-arounds
@@ -1690,52 +1674,52 @@
:CUSTOM_ID: group-lines
:END:
- There is not yet an easy way to access groups configured in the
- gpg.conf file from within GPGME. As a consequence these central
- groupings of keys cannot be shared amongst multiple programs, such
- as MUAs readily.
+There is not yet an easy way to access groups configured in the
+gpg.conf file from within GPGME. As a consequence these central
+groupings of keys cannot be shared amongst multiple programs, such as
+MUAs readily.
- The following code, however, provides a work-around for obtaining
- this information in Python.
+The following code, however, provides a work-around for obtaining this
+information in Python.
- #+begin_src python
- import subprocess
+#+begin_src python
+ import subprocess
- lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines()
+ lines = subprocess.getoutput("gpgconf --list-options gpg").splitlines()
- for i in range(len(lines)):
- if lines[i].startswith("group") is True:
- line = lines[i]
- else:
- pass
+ for i in range(len(lines)):
+ if lines[i].startswith("group") is True:
+ line = lines[i]
+ else:
+ pass
- groups = line.split(":")[-1].replace('"', '').split(',')
+ groups = line.split(":")[-1].replace('"', '').split(',')
- group_lines = []
- group_lists = []
+ group_lines = []
+ group_lists = []
- for i in range(len(groups)):
- group_lines.append(groups[i].split("="))
- group_lists.append(groups[i].split("="))
+ for i in range(len(groups)):
+ group_lines.append(groups[i].split("="))
+ group_lists.append(groups[i].split("="))
- for i in range(len(group_lists)):
- group_lists[i][1] = group_lists[i][1].split()
- #+end_src
+ for i in range(len(group_lists)):
+ group_lists[i][1] = group_lists[i][1].split()
+#+end_src
- The result of that code is that =group_lines= is a list of lists
- where =group_lines[i][0]= is the name of the group and
- =group_lines[i][1]= is the key IDs of the group as a string.
+The result of that code is that =group_lines= is a list of lists where
+=group_lines[i][0]= is the name of the group and =group_lines[i][1]=
+is the key IDs of the group as a string.
- The =group_lists= result is very similar in that it is a list of
- lists. The first part, =group_lists[i][0]= matches
- =group_lines[i][0]= as the name of the group, but
- =group_lists[i][1]= is the key IDs of the group as a string.
+The =group_lists= result is very similar in that it is a list of
+lists. The first part, =group_lists[i][0]= matches
+=group_lines[i][0]= as the name of the group, but =group_lists[i][1]=
+is the key IDs of the group as a string.
- A demonstration of using the =groups.py= module is also available
- in the form of the executable =mutt-groups.py= script. This second
- script reads all the group entries in a user's =gpg.conf= file and
- converts them into crypt-hooks suitable for use with the Mutt and
- Neomutt mail clients.
+A demonstration of using the =groups.py= module is also available in
+the form of the executable =mutt-groups.py= script. This second
+script reads all the group entries in a user's =gpg.conf= file and
+converts them into crypt-hooks suitable for use with the Mutt and
+Neomutt mail clients.
* Copyright and Licensing
@@ -1749,7 +1733,7 @@
:CUSTOM_ID: copyright
:END:
- Copyright © The GnuPG Project, 2018.
+Copyright © The GnuPG Project, 2018.
** License GPL compatible
@@ -1757,14 +1741,14 @@
:CUSTOM_ID: license
:END:
- This file is free software; as a special exception the author gives
- unlimited permission to copy and/or distribute it, with or without
- modifications, as long as this notice is preserved.
+This file is free software; as a special exception the author gives
+unlimited permission to copy and/or distribute it, with or without
+modifications, as long as this notice is preserved.
- This file is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY, to the extent permitted by law; without even
- the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- PURPOSE.
+This file is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.
* Footnotes