diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 43a0309..6f800c9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -21,6 +21,12 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: x64 + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + default: true - name: Lint code run: | make pydep diff --git a/Cargo.lock b/Cargo.lock index 7e5ad49..bf923fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,9 +418,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.127" +version = "0.2.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" +checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" [[package]] name = "lock_api" @@ -692,18 +692,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", diff --git a/Makefile b/Makefile index 46ce6c7..bdb0ffa 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,9 @@ fmt: .PHONY: lint lint: cargo fmt --all -- --check - cargo clippy --all-targets -- -D warnings --no-deps + # temporarily allow borrow-deref-ref until this issue is resolved: + # https://github.com/rust-lang/rust-clippy/issues/8971 + cargo clippy --all-targets -- -D warnings --no-deps -A clippy::borrow-deref-ref flake8 . .PHONY: clean diff --git a/README.md b/README.md index 1ff1e66..b01605f 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,226 @@ -hybrid-pke +Hybrid PKE =============== The Hybrid Public Key Encryption (HPKE) standard in Python. -[`hpke-rs`](https://crates.io/crates/hpke-rs) :handshake: [`PyO3`](https://github.com/PyO3/pyo3) +`hybrid_pke` = [`hpke-rs`](https://github.com/franziskuskiefer/hpke-rs) :heavy_plus_sign: [`PyO3`](https://github.com/PyO3/pyo3) This library provides Python bindings to the `hpke-rs` crate, which supports primitives from either [Rust Crypto](https://github.com/RustCrypto) or [EverCrypt](https://hacl-star.github.io/HaclValeEverCrypt.html). +
+ Table of Contents +
    +
  1. Usage
  2. +
  3. Features
  4. +
  5. Installation
  6. +
  7. Development
  8. +
  9. Related Projects
  10. +
+
+ +## Usage +### Basic +The single-shot API is intended for single message encryption/decryption. The default HPKE configuration uses the unauthenticated Base mode, an X25519 DH key encapsulation mechanism, a SHA256 key derivation mechanism, and a ChaCha20Poly1305 AEAD function. + +```python +import hybrid_pke + +hpke = hybrid_pke.default() +info = b"" # shared metadata, correspondance-level +aad = b"" # shared metadata, message-level +secret_key_r, public_key_r = hpke.generate_key_pair() # receiver keys, pre-generated + +# ============== Sender ============== + +message = b"hello from the other side!" +encap, ciphertext = hpke.seal(public_key_r, info, aad, message) + +# ============= Receiver ============= + +plaintext = hpke.open(encap, secret_key_r, info, aad, ciphertext) +print(plaintext.decode("utf-8")) +# >> hello from the other side! +``` + +### Advanced + +
Sender & Receiver Contexts + +The Sender Context and Receiver Context APIs allow for setting up a context for repeated encryptions and decryptions. It's recommended whenever you intend to perform several encryptions or decryptions in quick succession. +```python +info = b"quotes from your favorite aphorists" +aads = [ + b"Szasz", + b"Nietzsche", + b"Morandotti", + b"Brudzinski", + b"Hubbard", +] + +# ============== Sender ============== + +messages = [ + b"Two wrongs don't make a right, but they make a good excuse.", + b"Become who you are!", + b"Only those who aren't hungry are able to judge the quality of a meal.", + b"Under certain circumstances a wanted poster is a letter of recommendation.", + b"Nobody ever forgets where he buried the hatchet.", +] +encap, sender_context = hpke.setup_sender(public_key_r, info) + +ciphertexts = [] +for aad, msg in zip(aads, messages): + ciphertext = sender_context.seal(aad, msg) + ciphertexts.append(ciphertext) + +# ============= Receiver ============= + +receiver_context = hpke.setup_receiver(encap, secret_key_r, info) +plaintexts = [] +for aad, ctxt in zip(aads, ciphertexts): + plaintext = receiver_context.open(aad, ctxt) + plaintexts.append(plaintext) + +print(f"\"{plaintexts[0].decode()}\" - {aad[0].decode()}") +print(f"\"{plaintexts[1].decode()}\" - {aad[1].decode()}") +# >> "Two wrongs don't make a right, but they make a good excuse." - Szasz +# >> "Become who you are!" - Nietzsche +``` +
+ +
Mode.AUTH: Authenticated Sender + +Auth mode allows for signing and verifying encryptions with a previously authenticated sender key-pair. +```python +hpke = hybrid_pke.default(mode=hybrid_pke.Mode.AUTH) +secret_key_r, public_key_r = hpke.generate_key_pair() # receiver keys +secret_key_s, public_key_s = hpke.generate_key_pair() # sender keys, pre-authenticated + +# ============== Sender ============== + +# sign with sender's secret key +encap, ciphertext = hpke.seal(public_key_r, info, aad, message, sk_s=secret_key_s) + +# ============= Receiver ============= + +# verify with sender's public key +plaintext = hpke.open(encap, secret_key_r, info, aad, ciphertext, pk_s=public_key_s) +``` +
+ +
Mode.PSK: Pre-shared Key Authentication + +PSK mode allows for signing and verifying encryptions with a previously shared key held by both the sender and recipient. +```python +hpke = hybrid_pke.default(mode=hybrid_pke.Mode.PSK) +# pre-shared key + ID +psk = bytes.fromhex("0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82") +psk_id = bytes.fromhex("456e6e796e20447572696e206172616e204d6f726961") + +# ============== Sender ============== + +# sign with pre-shared key +encap, ciphertext = hpke.seal(public_key_r, info, aad, message, psk=psk, psk_id=psk_id) + +# ============= Receiver ============= + +# verify with pre-shared key +plaintext = hpke.open(encap, secret_key_r, info, aad, ciphertext, psk=psk, psk_id=psk_id) +``` +
+ +
Mode.AUTH_PSK: Combining AUTH and PSK. + +PSK mode allows for signing and verifying encryptions with a previously shared key held by both the sender and recipient. +```python +hpke = hybrid_pke.default(mode=hybrid_pke.Mode.PSK) +secret_key_r, public_key_r = hpke.generate_key_pair() # receiver keys +secret_key_s, public_key_s = hpke.generate_key_pair() # sender keys, pre-authenticated +# pre-shared key + ID +psk = bytes.fromhex("0247fd33b913760fa1fa51e1892d9f307fbe65eb171e8132c2af18555a738b82") +psk_id = bytes.fromhex("456e6e796e20447572696e206172616e204d6f726961") + +# ============== Sender ============== + +# sign with both pre-shared key and sender's secret key +encap, ciphertext = hpke.seal( + public_key_r, info, aad, message, + psk=psk, psk_id=psk_id, sk_s=secret_key_s, +) + +# ============= Receiver ============= + +# verify with both pre-shared key and sender's public key +plaintext = hpke.open( + encap, secret_key_r, info, aad, ciphertext, + psk=psk, psk_id=psk_id, pk_s=public_key_s, +) +``` +
+ +

(back to top)

+ ## Features -The modes and features available match those supported by `hpke-rs`. - - - Modes - - [x] mode_base - - [x] mode_psk - - [x] mode_auth - - [x] mode_auth_psk - - AEADs - - [x] AES-128-GCM - - [x] AES-256-GCM - - [x] ChaCha20Poly1305 - - [x] Export only - - KEMs - - [x] DHKEM(P-256, HKDF-SHA256) - - [ ] DHKEM(P-384, HKDF-SHA384) - - [ ] DHKEM(P-521, HKDF-SHA512) - - [x] DHKEM(X25519, HKDF-SHA256) - - [ ] DHKEM(X448, HKDF-SHA512) - - KDFs - - [x] HKDF-SHA256 - - [x] HKDF-SHA384 - - [x] HKDF-SHA512 +The features available match those supported by `hpke-rs`. +
HPKE Modes + +- [x] mode_base +- [x] mode_psk +- [x] mode_auth +- [x] mode_auth_psk +
+ +
KEMs: (Diffie-Hellman) Key Encapsulation Mechanisms + +- [x] DHKEM(P-256, HKDF-SHA256) +- [ ] DHKEM(P-384, HKDF-SHA384) +- [ ] DHKEM(P-521, HKDF-SHA512) +- [x] DHKEM(X25519, HKDF-SHA256) +- [ ] DHKEM(X448, HKDF-SHA512) +
+ +
KDFs: Key Derivation Functions + +- [x] HKDF-SHA256 +- [x] HKDF-SHA384 +- [x] HKDF-SHA512 +
+ +
AEADs: Authenticated Encryption with Additional Data functions + +- [x] AES-128-GCM +- [x] AES-256-GCM +- [x] ChaCha20Poly1305 +- [x] Export only +
+ +

(back to top)

## Installation Wheels for various platforms and architectures can be found on [PyPI](https://pypi.org/project/hybrid-pke/) or in the `wheelhouse.zip` archive from the [latest Github release](https://github.com/capeprivacy/hybrid-pke/releases). The library can also be installed from source with [`maturin`](https://github.com/PyO3/maturin) -- see below. +

(back to top)

+ ## Development We use [`maturin`](https://github.com/PyO3/maturin) to build and distribute the PyO3 extension module as a Python wheel. For users of `cmake`, we provide a [`Makefile`](https://github.com/capeprivacy/hybrid-pke/blob/main/Makefile) that includes some helpful development commands. -Other useful tips: +
Some useful tips + - `maturin develop` builds & installs the Python package into your Python environment (`venv` or `conda` recommended) -- `pytest .` tests the resulting Python package +- `pytest .` tests the resulting Python package. +- `pytest -n auto .` runs the full test suite in parallel. - `maturin build --release -o dist --sdist` builds the extension module in release-mode and produces a wheel for your environment's OS and architecture. - The `-i`/`--interpreter` flag for `maturin` can be used to swap out different Python interpreters, if you have multiple Python installations. +
+ +

(back to top)

## Related Projects - [hpke-py](https://github.com/ctz/hpke-py): An implementation of HPKE based on primitives from [cryptography.io](https://cryptography.io). + +

(back to top)

\ No newline at end of file