Introduction to johnnycanencrypt

Johnnycanencrypt provides only a few selected operations from the OpenPGP spec. If you need more and better granular access to the operations, this may not be the module for you.

The module has 2 parts, one high level API, which can be directly accessed via importing the module. There is another internal module with the same name (which is native in Rust), and has all the low level functions and classes.

We will import the module as jce.

>>> import johnnycanencrypt as jce

The KeyStore

The module interacts over a KeyStore object, which points to a directory path on the system. Inside of the directory, it will create a jce.db sqlite3 database if missing. Below is an example where we are creating a keystore in an empty directory at “/var/lib/myapplication/keys”, and then we will import a few keys in there. For our example, we will use the keys from our tests directory.

>>> ks = jce.KeyStore("/var/lib/myapplication/keys")
>>> ks.import_key("tests/files/store/secret.asc")
<Key fingerprint=F4F388BBB194925AE301F844C52B42177857DD79 type=SECRET>
>>> ks.import_key("tests/files/store/pgp_keys.asc")
<Key fingerprint=A85FF376759C994A8A1168D8D8219C8C43F6C5E1 type=PUBLIC>
>>> ks.import_key("tests/files/store/public.asc")
<Key fingerprint=F4F388BBB194925AE301F844C52B42177857DD79 type=PUBLIC>

Now, if we check the directory from the shell, we will find the keys imported there.

❯ ls -l /var/lib/myapplication/keys
.rw-rw-r--@ 9.5k user  6 Oct 18:48 jce.db

Note

This keystore directory is very much application specific. As a developer you should choose which directory on the system you will use as the key store. SecureDrop uses /var/lib/securedrop/store as their key storage (via gpg’s python binding).

Warning

This module does not handle keys still using sha1 or md5 for hash algorithms. If you are using any such old key, please generate new key and use them along wtih the module. This function explains in some details why.

KeyStore path for the applicaitons which can run per user

If you are writing a desktop application or any other tool which can have per user configuration, you should look into the base dir spec. If your application name is myapplication, then the store path can be like: $XDG_DATA_HOME/myapplication.

Encrypting and decrypting some bytes for a given fingerprint

>>> ks = jce.KeyStore("tests/files/store")
>>> key = ks.get_key("F4F388BBB194925AE301F844C52B42177857DD79")
>>> enc = ks.encrypt(key, "Sequoia is amazing.")
>>> print(enc[:27])
b'-----BEGIN PGP MESSAGE-----'
>>> text = ks.decrypt(key, enc, "redhat")
>>> print(text)
b'Sequoia is amazing.'

Verify Tor Browser download using the signature and public key

In this example we will download the Tor Browser 10.0, and the signature and the public key using wget, and then verify via our module.

curl -s -O https://dist.torproject.org/torbrowser/12.0.5/tor-browser-linux64-12.0.5_ALL.tar.xz
curl -s -O https://dist.torproject.org/torbrowser/12.0.5/tor-browser-linux64-12.0.5_ALL.tar.xz.asc
KEYURL=https://openpgpkey.torproject.org/.well-known/openpgpkey/torproject.org/hu/kounek7zrdx745qydx6p59t9mqjpuhdf
curl -s -o kounek7zrdx745qydx6p59t9mqjpuhdf.pub $KEYURL

Now let us import the key and verify.

import tempfile
import johnnycanencrypt as jce


filename = "tor-browser-linux64-12.0.5_ALL.tar.xz"
signame = "tor-browser-linux64-12.0.5_ALL.tar.xz.asc"

with tempfile.TemporaryDirectory() as tmpdir:
    ks = jce.KeyStore(tmpdir)
    torkey = ks.import_key("kounek7zrdx745qydx6p59t9mqjpuhdf.pub")
    if ks.verify_file_detached(torkey, filename, signame):
        print("Verified.")
    else:
        print("Verification failed.")