Skip to content

Crypt4GH Processing

A Crypt4GH container processing library.

Copyright (c) 2024 Dominik Pantůček dominik.pantucek@trustica.cz

Distributed under MIT license - see LICENSE for details.

Introduction

The primary aim of this library is to allow the users to manage reader keys of Crypt4GH containers - mainly add readers to containers or their parts which can be read using provided keys. Any additional functionality is provided mainly as a means of implementing the primary features.

Public API

Working with Crypt4GH Keys

The C4GHKey class provides loaders from file, string, bytes and stream. For loading public keys only the key source is needed:

from oarepo_c4gh import C4GHKey

my_key = C4GHKey.from_file("my_key.c4gh")

Private keys on the other hand are usually encrypted using symmetric cipher and a password based key. All the loaders accept a callback function as an optional second argument which should return the password to be tried when called:

my_secret_key = C4GHKey.from_file("my_secret_key.c4gh", lambda: "password")

Once the key is loaded, one can always obtain its public part:

print(my_key.get_public_key())
print(my_secret_key.get_public_key())

Loading Crypt4GH Containers

With secret key loaded, initializing Crypt4GH container for reading with actual container data is straightforward:

from oarepo_c4gh import Crypt4GH

with open("hello.txt.c4gh") as f:
    container = Crypt4GH(my_secret_key, f)

To process the data blocks from the initialized container a single-use iterator is provided:

for block in container.data_blocks:
    if block.is_deciphered:
        print(block.cleartext)
    else:
        print("Cannot decrypt this block.")

If only deciphered blocks are to be processed, the clear_blocks iterator can be used:

for block in container.clear_blocks:
    print(block.cleartext)

Multiple Crypt4GH Keys Support

The reader may try multiple reader keys when reading the container header. To work with multiple keys, a key collection has to be created and subsequently used:

from oarepo_c4gh import KeyCollection

my_secret_key = C4GHKey.from_file("my_secret_key.c4gh", lambda: "password")
my_other_secret_key = C4GHKey.from_file(
  "my_other_secret_key.c4gh",
  lambda: "other_password"
)
my_keys = KeyCollection(my_secret_key, my_other_secret_key)
with open("hello.txt.c4gh") as f:
    container = Crypt4GH(my_keys, f)

Container Serialization

An initialized container can be also serialized. Currently the result of such serialization should be exactly the same binary data as the original input.

from oarepo_cg4h import Crypt4GHWriter

writer = Crypt4GHWriter(container, open("output.c4gh", "w"))
writer.write()

Adding Recipients for Serialization

For granting access to the encrypted container contents a filtering wrapper is implemented which accepts underlying Crypt4GH container as its source and allows for arbitrary transformations on-the-fly. Currently only the add_recipient transformation is available, which adds given public key as a new recipient to the container by encrypting every readable packet for this recipient and adding the newly encrypted version of given packet to the output.

from oarepo_c4gh import Crypt4GHFilter

my_secret_key = C4GHKey.from_file("my_secret_key.c4gh", lambda: "password")
my_other_secret_key = C4GHKey.from_file(
  "my_other_secret_key.c4gh",
  lambda: "other_password"
)
my_keys = KeyCollection(my_secret_key, my_other_secret_key)
orig_container = Crypt4GH(my_keys, open("hello.txt.c4gh"))
new_container = AddRecipientFilter(orig_container, alice_pub)
writer = Crypt4GHWriter(new_container, open("output.c4gh", "w"))
writer.write()