doc: python bindings howto

* deconstructing multi-recipient encryption.
This commit is contained in:
Ben McGinnes 2018-03-19 10:39:53 +11:00
parent 64c5886132
commit 1779d7b9d6

View File

@ -22,6 +22,7 @@
This document provides basic instruction in how to use the GPGME This document provides basic instruction in how to use the GPGME
Python bindings to programmatically leverage the GPGME library. Python bindings to programmatically leverage the GPGME library.
** Python 2 versus Python 3 ** Python 2 versus Python 3
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: py2-vs-py3 :CUSTOM_ID: py2-vs-py3
@ -53,6 +54,7 @@
:CUSTOM_ID: gpgme-concepts :CUSTOM_ID: gpgme-concepts
:END: :END:
** A C API ** A C API
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: gpgme-c-api :CUSTOM_ID: gpgme-c-api
@ -72,6 +74,7 @@
languages. This is where the need for bindings in various languages. This is where the need for bindings in various
languages stems. languages stems.
** Python bindings ** Python bindings
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: gpgme-python-bindings :CUSTOM_ID: gpgme-python-bindings
@ -88,6 +91,7 @@
tied to the exact same version of GPGME used to generate that copy tied to the exact same version of GPGME used to generate that copy
of =gpgme.h=. of =gpgme.h=.
** Difference between the Python bindings and other GnuPG Python packages ** Difference between the Python bindings and other GnuPG Python packages
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: gpgme-python-bindings-diffs :CUSTOM_ID: gpgme-python-bindings-diffs
@ -97,6 +101,7 @@
over the years. Some of the most well known are listed here, along over the years. Some of the most well known are listed here, along
with what differentiates them. with what differentiates them.
*** The python-gnupg package maintained by Vinay Sajip *** The python-gnupg package maintained by Vinay Sajip
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: diffs-python-gnupg :CUSTOM_ID: diffs-python-gnupg
@ -116,6 +121,7 @@
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 *** The gnupg package created and maintained by Isis Lovecruft
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: diffs-isis-gnupg :CUSTOM_ID: diffs-isis-gnupg
@ -136,6 +142,7 @@
The gnupg package is available under the GNU General Public The gnupg package is available under the GNU General Public
License version 3.0 (or later). License version 3.0 (or later).
*** The PyME package maintained by Martin Albrecht *** The PyME package maintained by Martin Albrecht
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: diffs-pyme :CUSTOM_ID: diffs-pyme
@ -168,6 +175,7 @@
:CUSTOM_ID: gpgme-python-install :CUSTOM_ID: gpgme-python-install
:END: :END:
** No PyPI ** No PyPI
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: do-not-use-pypi :CUSTOM_ID: do-not-use-pypi
@ -187,6 +195,7 @@
bundled with it or a full implementation of C for each bundled with it or a full implementation of C for each
architecture. architecture.
** Requirements ** Requirements
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: gpgme-python-requirements :CUSTOM_ID: gpgme-python-requirements
@ -201,6 +210,7 @@
3. GPGME itself. Which also means that all of GPGME's dependencies 3. GPGME itself. Which also means that all of GPGME's dependencies
must be installed too. must be installed too.
** Installation ** Installation
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: installation :CUSTOM_ID: installation
@ -225,6 +235,7 @@
For Python 3 it checks for these executables in this order: For Python 3 it checks for these executables in this order:
=python3=, =python3.6=, =python3.5= and =python3.4=. =python3=, =python3.6=, =python3.5= and =python3.4=.
*** Installing GPGME *** Installing GPGME
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: install-gpgme :CUSTOM_ID: install-gpgme
@ -243,6 +254,7 @@
regarding GPGME's design which hold true whether you're dealing with regarding GPGME's design which hold true whether you're dealing with
the C code directly or these Python bindings. the C code directly or these Python bindings.
** No REST ** No REST
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: no-rest-for-the-wicked :CUSTOM_ID: no-rest-for-the-wicked
@ -266,6 +278,7 @@
direct bindings and it's this pythonic layer with which this HOWTO direct bindings and it's this pythonic layer with which this HOWTO
deals with. deals with.
** Context ** Context
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: howto-get-context :CUSTOM_ID: howto-get-context
@ -294,6 +307,7 @@
:CUSTOM_ID: howto-keys :CUSTOM_ID: howto-keys
:END: :END:
** Key selection ** Key selection
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: howto-keys-selection :CUSTOM_ID: howto-keys-selection
@ -560,10 +574,11 @@
if rpattern[i].can_encrypt == 1: if rpattern[i].can_encrypt == 1:
logrus.append(rpattern[i]) logrus.append(rpattern[i])
cipher = 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: with open("secret_plans.txt.asc", "wb") as afile:
afile.write(cipher[0]) afile.write(ciphertext)
#+end_src #+end_src
All it would take to change the above example to sign the message All it would take to change the above example to sign the message
@ -571,8 +586,9 @@
be to change the =c.encrypt= line to this: be to change the =c.encrypt= line to this:
#+begin_src python #+begin_src python
cipher = c.encrypt(text, recipients=logrus, always_trust=True, ciphertext, result, sign_result = c.encrypt(text, recipients=logrus,
add_encrypt_to=True) always_trust=True,
add_encrypt_to=True)
#+end_src #+end_src
The only keyword arguments requiring modification are those for The only keyword arguments requiring modification are those for
@ -600,7 +616,7 @@
logrus.append(rpattern[i]) logrus.append(rpattern[i])
try: try:
cipher = c.encrypt(text, recipients=logrus, add_encrypt_to=True) ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, add_encrypt_to=True)
except gpg.errors.InvalidRecipients as e: except gpg.errors.InvalidRecipients as e:
for i in range(len(e.recipients)): for i in range(len(e.recipients)):
for n in range(len(logrus)): for n in range(len(logrus)):
@ -609,12 +625,12 @@
else: else:
pass pass
try: try:
cipher = c.encrypt(text, recipients=logrus, add_encrypt_to=True) ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, add_encrypt_to=True)
except: except:
pass pass
with open("secret_plans.txt.asc", "wb") as afile: with open("secret_plans.txt.asc", "wb") as afile:
afile.write(cipher[0]) afile.write(ciphertext)
#+end_src #+end_src
This will attempt to encrypt to all the keys searched for, then This will attempt to encrypt to all the keys searched for, then
@ -721,7 +737,7 @@
text = text0.encode() text = text0.encode()
c = gpg.Context(armor=True, signers=sig_src) c = gpg.Context(armor=True, signers=sig_src)
signed = c.sign(text, mode=0) signed = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
with open("/path/to/statement.txt.asc", "w") as afile: with open("/path/to/statement.txt.asc", "w") as afile:
for line in signed[0]: for line in signed[0]:
@ -740,7 +756,7 @@
text = tfile.read() text = tfile.read()
c = gpg.Context() c = gpg.Context()
signed = c.sign(text, mode=0) signed = c.sign(text, mode=gpg.constants.sig.mode.NORMAL)
with open("/path/to/statement.txt.sig", "wb") as afile: with open("/path/to/statement.txt.sig", "wb") as afile:
afile.write(signed[0]) afile.write(signed[0])
@ -766,7 +782,7 @@
text = text0.encode() text = text0.encode()
c = gpg.Context(armor=True) c = gpg.Context(armor=True)
signed = c.sign(text, mode=1) signed = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
with open("/path/to/statement.txt.asc", "w") as afile: with open("/path/to/statement.txt.asc", "w") as afile:
for line in signed[0].splitlines(): for line in signed[0].splitlines():
@ -783,7 +799,7 @@
text = tfile.read() text = tfile.read()
c = gpg.Context(signers=sig_src) c = gpg.Context(signers=sig_src)
signed = c.sign(text, mode=1) signed = c.sign(text, mode=gpg.constants.sig.mode.DETACH)
with open("/path/to/statement.txt.sig", "wb") as afile: with open("/path/to/statement.txt.sig", "wb") as afile:
afile.write(signed[0]) afile.write(signed[0])
@ -809,7 +825,7 @@
text = text0.encode() text = text0.encode()
c = gpg.Context() c = gpg.Context()
signed = c.sign(text, mode=2) signed = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
with open("/path/to/statement.txt.asc", "w") as afile: with open("/path/to/statement.txt.asc", "w") as afile:
for line in signed[0].splitlines(): for line in signed[0].splitlines():
@ -826,7 +842,7 @@
text = tfile.read() text = tfile.read()
c = gpg.Context() c = gpg.Context()
signed = c.sign(text, mode=2) signed = c.sign(text, mode=gpg.constants.sig.mode.CLEAR)
with open("/path/to/statement.txt.asc", "wb") as afile: with open("/path/to/statement.txt.asc", "wb") as afile:
afile.write(signed[0]) afile.write(signed[0])
@ -856,14 +872,15 @@
c = gpg.Context() c = gpg.Context()
try: try:
verified = c.verify(open(gpg_file)) data, result = c.verify(open(gpg_file))
verified = True
except gpg.errors.BadSignatures as e: except gpg.errors.BadSignatures as e:
verified = None verified = False
print(e) print(e)
if verified is not None: if verified is True:
for i in range(len(verified[1].signatures)): for i in range(len(result.signatures)):
sign = verified[1].signatures[i] sign = result.signatures[i]
print("""Good signature from: print("""Good signature from:
{0} {0}
with key {1} with key {1}
@ -887,14 +904,15 @@
c = gpg.Context() c = gpg.Context()
try: try:
verified = c.verify(open(asc_file)) data, result = c.verify(open(asc_file))
verified = True
except gpg.errors.BadSignatures as e: except gpg.errors.BadSignatures as e:
verified = None verified = False
print(e) print(e)
if verified is not None: if verified is True:
for i in range(len(verified[1].signatures)): for i in range(len(result.signatures)):
sign = verified[1].signatures[i] sign = result.signatures[i]
print("""Good signature from: print("""Good signature from:
{0} {0}
with key {1} with key {1}
@ -906,15 +924,14 @@
#+end_src #+end_src
In both of the previous examples it is also possible to compare the In both of the previous examples it is also possible to compare the
original data that was signed against the signed data in original data that was signed against the signed data in =data= to
=verified[0]= to see if it matches with something like this: see if it matches with something like this:
#+begin_src python #+begin_src python
afile = open(filename, "rb") with open(filename, "rb") as afile:
text = afile.read() text = afile.read()
afile.close()
if text == verified[0]: if text == data:
print("Good signature.") print("Good signature.")
else: else:
pass pass
@ -923,8 +940,8 @@
The following two examples, however, deal with detached signatures. The following two examples, however, deal with detached signatures.
With his method of verification the data that was signed does not With his method of verification the data that was signed does not
get returned since it is already being explicitly referenced in the get returned since it is already being explicitly referenced in the
first argument of =c.verify=. So =verified[0]= is None and only first argument of =c.verify=. So =data= is =None= and only the
the data in =verified[1]= is available. information in =result= is available.
#+begin_src python #+begin_src python
import gpg import gpg
@ -936,14 +953,15 @@
c = gpg.Context() c = gpg.Context()
try: try:
verified = c.verify(open(filename), open(sig_file)) data, result = c.verify(open(filename), open(sig_file))
verified = True
except gpg.errors.BadSignatures as e: except gpg.errors.BadSignatures as e:
verified = None verified = False
print(e) print(e)
if verified is not None: if verified is True:
for i in range(len(verified[1].signatures)): for i in range(len(result.signatures)):
sign = verified[1].signatures[i] sign = result.signatures[i]
print("""Good signature from: print("""Good signature from:
{0} {0}
with key {1} with key {1}
@ -964,14 +982,15 @@
c = gpg.Context() c = gpg.Context()
try: try:
verified = c.verify(open(filename), open(asc_file)) data, result = c.verify(open(filename), open(asc_file))
verified = True
except gpg.errors.BadSignatures as e: except gpg.errors.BadSignatures as e:
verified = None verified = False
print(e) print(e)
if verified is not None: if verified is not None:
for i in range(len(verified[1].signatures)): for i in range(len(result.signatures)):
sign = verified[1].signatures[i] sign = result.signatures[i]
print("""Good signature from: print("""Good signature from:
{0} {0}
with key {1} with key {1}
@ -1008,11 +1027,8 @@
allow-secret-key-import allow-secret-key-import
trust-model tofu+pgp trust-model tofu+pgp
tofu-default-policy unknown tofu-default-policy unknown
# no-auto-check-trustdb
enable-large-rsa enable-large-rsa
enable-dsa2 enable-dsa2
# no-emit-version
# no-comments
# cert-digest-algo SHA256 # cert-digest-algo SHA256
cert-digest-algo SHA512 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 default-preference-list TWOFISH CAMELLIA256 AES256 CAMELLIA192 AES192 CAMELLIA128 AES BLOWFISH IDEA CAST5 3DES SHA512 SHA384 SHA256 SHA224 RIPEMD160 SHA1 ZLIB BZIP2 ZIP Uncompressed