From f0063afa71bc7e71f19d174acc2fde26f0c11850 Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Tue, 15 May 2018 13:13:16 +1000 Subject: docs: python bindings HOWTO - DITA XML version * Due to the org-babel bug which breaks Python source code examples beyond the most simple snippets, ported the HOWTO to a source format which I *know* for sure won't break it. * Details of the org-mode bug is in https://dev.gnupg.org/T3977 * DITA project uses DITA-OT 2.x (2.4 or 2.5, IIRC) with support for DITA 1.3. * source files were written with oXygenXML Editor 20.0, hence the oXygenXML project file in the directory; however only the .ditamap and .dita files are required to generate any output with the DITA-OT. Signed-off-by: Ben McGinnes --- .../docs/dita/howto/part04/basic-functions.dita | 12 ++ .../docs/dita/howto/part04/clear-signing.dita | 42 ++++++ lang/python/docs/dita/howto/part04/decryption.dita | 31 +++++ .../docs/dita/howto/part04/default-signing.dita | 51 +++++++ .../docs/dita/howto/part04/detached-signing.dita | 42 ++++++ .../docs/dita/howto/part04/encrypt-to-many.dita | 100 ++++++++++++++ .../docs/dita/howto/part04/encrypt-to-one.dita | 83 ++++++++++++ lang/python/docs/dita/howto/part04/encryption.dita | 12 ++ .../dita/howto/part04/signing-key-selection.dita | 28 ++++ lang/python/docs/dita/howto/part04/signing.dita | 11 ++ .../docs/dita/howto/part04/verification.dita | 150 +++++++++++++++++++++ 11 files changed, 562 insertions(+) create mode 100644 lang/python/docs/dita/howto/part04/basic-functions.dita create mode 100644 lang/python/docs/dita/howto/part04/clear-signing.dita create mode 100644 lang/python/docs/dita/howto/part04/decryption.dita create mode 100644 lang/python/docs/dita/howto/part04/default-signing.dita create mode 100644 lang/python/docs/dita/howto/part04/detached-signing.dita create mode 100644 lang/python/docs/dita/howto/part04/encrypt-to-many.dita create mode 100644 lang/python/docs/dita/howto/part04/encrypt-to-one.dita create mode 100644 lang/python/docs/dita/howto/part04/encryption.dita create mode 100644 lang/python/docs/dita/howto/part04/signing-key-selection.dita create mode 100644 lang/python/docs/dita/howto/part04/signing.dita create mode 100644 lang/python/docs/dita/howto/part04/verification.dita (limited to 'lang/python/docs/dita/howto/part04') diff --git a/lang/python/docs/dita/howto/part04/basic-functions.dita b/lang/python/docs/dita/howto/part04/basic-functions.dita new file mode 100644 index 00000000..a0c64b56 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/basic-functions.dita @@ -0,0 +1,12 @@ + + + + + Basic Functions + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/clear-signing.dita b/lang/python/docs/dita/howto/part04/clear-signing.dita new file mode 100644 index 00000000..c6104922 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/clear-signing.dita @@ -0,0 +1,42 @@ + + + + + Clear Signatures + +

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.

+

+ import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +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()) + +

+

In spite of the appearance of a clear-signed message, the data handled by GPGME in signing + it must still be byte literals.

+

+ import gpg + +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) + +with open("/path/to/statement.txt.asc", "wb") as afile: + afile.write(signed_data) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/decryption.dita b/lang/python/docs/dita/howto/part04/decryption.dita new file mode 100644 index 00000000..e3918c55 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/decryption.dita @@ -0,0 +1,31 @@ + + + + + Decryption + +

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.

+

+ import gpg + +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: + plaintext, result, verify_result = gpg.Context().decrypt(cfile) + +with open(newfile, "wb") as nfile: + nfile.write(plaintext) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/default-signing.dita b/lang/python/docs/dita/howto/part04/default-signing.dita new file mode 100644 index 00000000..d3a227b5 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/default-signing.dita @@ -0,0 +1,51 @@ + + + + + Default Signatures + +

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.

+

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.

+

+ import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +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()) + +

+

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.

+

+ import gpg + +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) + +with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/detached-signing.dita b/lang/python/docs/dita/howto/part04/detached-signing.dita new file mode 100644 index 00000000..38406eff --- /dev/null +++ b/lang/python/docs/dita/howto/part04/detached-signing.dita @@ -0,0 +1,42 @@ + + + + + Detached Signatures + +

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).

+

+ import gpg + +text0 = """Declaration of ... something. + +""" +text = text0.encode() + +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()) + +

+

As with normal signatures, detached signatures are best handled as byte literals, even when + the output is ASCII armoured.

+

+ import gpg + +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) + +with open("/path/to/statement.txt.sig", "wb") as afile: + afile.write(signed_data) + +

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/encrypt-to-many.dita b/lang/python/docs/dita/howto/part04/encrypt-to-many.dita new file mode 100644 index 00000000..df3454f8 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/encrypt-to-many.dita @@ -0,0 +1,100 @@ + + + + + Encrypting to Multiple Keys + +

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,You probably don't really want to do + this. Searching the keyservers for "gnupg.org" produces over 400 results, the majority of + which aren't actually at the gnupg.org domain, but just included a comment regarding the + project in their key somewhere. but does not encrypt to a default key or other + key which is configured to normally encrypt to.

+

+ import gpg + +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. + +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 = [] + +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) + +with open("secret_plans.txt.asc", "wb") as f: + f.write(ciphertext) + +

+

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:

+

+ ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + always_trust=True, + add_encrypt_to=True) + +

+

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:

+

+ import gpg + +with open("secret_plans.txt.asc", "rb") as f: + text = f.read() + +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 + try: + ciphertext, result, sign_result = c.encrypt(text, recipients=logrus, + add_encrypt_to=True) + except: + pass + +with open("secret_plans.txt.asc", "wb") as f: + f.write(ciphertext) + +

+

This will attempt to encrypt to all the keys searched for, then remove invalid recipients + if it fails and try again.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/encrypt-to-one.dita b/lang/python/docs/dita/howto/part04/encrypt-to-one.dita new file mode 100644 index 00000000..2abbe06a --- /dev/null +++ b/lang/python/docs/dita/howto/part04/encrypt-to-one.dita @@ -0,0 +1,83 @@ + + + + + Encrypting to One Key + +

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).
  • +
+

+

+ 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 f: + f.write(ciphertext) + +

+

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:

+

+ import gpg + +a_key = "0x12345678DEADBEEF" + +with open("secret_plans.txt", "rb") as f: + text = f.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) + +with open("secret_plans.txt.asc", "wb") as f: + f.write(ciphertext) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/encryption.dita b/lang/python/docs/dita/howto/part04/encryption.dita new file mode 100644 index 00000000..572cc9d2 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/encryption.dita @@ -0,0 +1,12 @@ + + + + + Encryption + +

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/signing-key-selection.dita b/lang/python/docs/dita/howto/part04/signing-key-selection.dita new file mode 100644 index 00000000..34d02b4a --- /dev/null +++ b/lang/python/docs/dita/howto/part04/signing-key-selection.dita @@ -0,0 +1,28 @@ + + + + + Signing Key Selection + +

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.

+

+ 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) + +

+

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.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/signing.dita b/lang/python/docs/dita/howto/part04/signing.dita new file mode 100644 index 00000000..289e3742 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/signing.dita @@ -0,0 +1,11 @@ + + + + + Signing Text and Files + +

The following sections demonstrate how to specify keys to sign with and the types of + signatures which can be made.

+ +
+
diff --git a/lang/python/docs/dita/howto/part04/verification.dita b/lang/python/docs/dita/howto/part04/verification.dita new file mode 100644 index 00000000..d50482a8 --- /dev/null +++ b/lang/python/docs/dita/howto/part04/verification.dita @@ -0,0 +1,150 @@ + + + + + Signature Verification + +

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:

+

+ 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 + +

+

Whereas this next example, which is almost identical would work with normal ASCII armoured + files and with clear-signed files:

+

+ 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 + +

+

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:

+

+ with open(filename, "rb") as afile: + text = afile.read() + +if text == data: + print("Good signature.") +else: + pass + +

+

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.

+

+ 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 + +

+

+ 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 not None: + 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 + +

+ +
+
-- cgit v1.2.3