CryptoVault-Python
A Python implementation of the cryptovault
blob format, byte-compatible with the Rust crate. AES-256-GCM-SIV under an HKDF-SHA256 key,
Argon2id key derivation and DEK/KEK key wrapping, followed by the crate's forward-error
correction: Reed-Solomon, an interleaver and a Viterbi code, or no FEC at all.
Status: 0.3.0, alpha. Every FEC strategy of
cryptovault0.3.0 is implemented and checked in both directions against the crate.
What it does
Two services that share a 32-byte key can encrypt and authenticate messages for each other. A blob made on either side opens on the other, Rust → Python and Python → Rust. The package opens no connections and knows nothing about transports: it turns text or key material into a blob and back, and the caller moves the blob.
The Rust crate stacks two independent layers: an AEAD that provides all of the security, then an FEC that adds channel resilience and no security. Both are implemented here, with the same four FEC strategies as the crate.
Installation
pip install cryptovault-py
The import name is cryptovault. Requirements lists the supported Python versions and the cryptography and
OpenSSL floor.
Quick start
>>> from cryptovault import CryptoVault, generate_dek, generate_salt
>>> vault = CryptoVault.default() # Argon2id + AES-256-GCM-SIV + concatenated FEC
>>> key = generate_dek() # or vault.derive_key(password, salt)
>>> blob = vault.encrypt_with_key(key, "sk-secret")
>>> vault.decrypt_with_key(key, blob)
'sk-secret'
>>> salt = generate_salt() # one per context, kept by the caller
>>> kek = generate_dek()
>>> dek = generate_dek()
>>> wrapped = vault.wrap_key(kek, salt, dek) # the salt is bound as AAD
>>> vault.unwrap_key(kek, salt, wrapped) == dek
True
CryptoVault.default() matches CryptoVault::default() in Rust. To pick another FEC
strategy, compose the vault yourself; the constructor has no defaults:
>>> from cryptovault import Aes256GcmSivCipher, Argon2Kdf, NoFec
>>> aead_only = CryptoVault(Argon2Kdf(), Aes256GcmSivCipher(), NoFec())
>>> aead_only.decrypt_with_key(key, aead_only.encrypt_with_key(key, "hi"))
'hi'
derive_key(password, salt) runs Argon2id with the crate's OWASP-2025 profile (64 MiB, three
passes, four lanes). Call it once per session and keep the key.
Blob layout
blob = base64( fec.encode( header ‖ nonce(12 B) ‖ ciphertext ‖ tag(16 B) ) ) standard, padded
header = version (0x01) ‖ plaintext_len (u32, little-endian)
key = HKDF-SHA256( salt = none, ikm = 32-byte master key, info = "cryptovault:v1:aead", L = 32 )
AAD = purpose ‖ header ‖ aad_extra
purpose = 0x01 data · 0x02 wrapped key
extra = empty for data · the salt for a wrapped key (authenticated, never stored)
cipher = AES-256-GCM-SIV, fresh random 12-byte nonce per message
The header is bound as associated data, so changing any bit of it fails authentication. The purpose byte keeps a data blob from opening as a wrapped key, and
the other way round. As for the version byte, it leaves room for the format to evolve; today
only 0x01 is accepted.
FEC strategies
The FEC layer sits after the AEAD and only helps a blob survive a noisy channel. Correction is all or nothing. Within capacity the blob comes back exact; past it, decryption fails with a typed error and never returns wrong plaintext, because the AEAD tag checks the result.
| Strategy | Pipeline | Corrects | Size of a 1 KiB message |
|---|---|---|---|
ConcatenatedFec.default() |
RS(255, 223) → depth-5 block interleaver → Viterbi K=7 R=1/2 | scattered bit errors and bursts | 3404 B |
ConcatenatedFec.with_csprng_from_master(master, depth) |
the same, plus a keyed shuffle per window | as above, weaker burst spreading | 3404 B |
ViterbiOnlyFec() |
Viterbi K=7 R=1/2 | scattered bit errors | 2824 B |
ReedSolomonCodec() |
RS(255, 223) | up to 16 bad bytes per 255-byte codeword | 1700 B |
NoFec() |
none | nothing; any change fails authentication | 1412 B |
The CSPRNG variant derives its seed from the master key with HKDF (info = "cryptovault:v1:interleaver"), and both ends must build it from the same master and depth.
It is obfuscation, not security.
Speed
The Reed-Solomon and Viterbi codecs come in two implementations that produce the same bytes.
The platform wheels carry a compiled extension, cryptovault._native, built from the
reedsolomon 0.2.0 and viterbi 0.2.0 crates that cryptovault itself uses. Everywhere
else the package runs pure-Python ports of those crates. A platform with no compiled wheel
gets the py3-none-any one, and on an interpreter the abi3 wheels do not cover (PyPy,
free-threaded CPython) the extension does not load. Nothing needs configuring;
cryptovault.fec._codecs.ACTIVE.name says which one is running. Measured with CPython 3.11
on Windows:
| Strategy | Codecs | Encrypt 128 KiB | Decrypt 128 KiB | Decrypt 128 KiB, one flipped bit | Decrypt 1 KiB, one flipped bit |
|---|---|---|---|---|---|
ConcatenatedFec |
compiled | 0.21 s | 0.22 s | 2.2 s | 0.02 s |
ConcatenatedFec |
Python | 1.4 s | 1.7 s | 15.6 s | 0.16 s |
ViterbiOnlyFec |
compiled | 0.03 s | 0.05 s | 2.6 s | 0.02 s |
ViterbiOnlyFec |
Python | 0.25 s | 0.66 s | 14.3 s | 0.12 s |
ReedSolomonCodec |
compiled | 0.02 s | 0.04 s | 0.04 s | < 0.01 s |
ReedSolomonCodec |
Python | 1.1 s | 1.0 s | 0.82 s | 0.01 s |
NoFec |
none | < 0.01 s | < 0.01 s | fails, < 0.01 s | fails, < 0.01 s |
A clean Viterbi sub-block decodes directly with either implementation. One with channel errors runs the full trellis: about 2 s for a full 124 950-byte sub-block with the compiled codecs, the speed of the crate's own decoder, and about 12 s in Python.
Security model
| Property | Mechanism | Level |
|---|---|---|
| Confidentiality | AES-256-GCM-SIV (RFC 8452) | 256-bit key |
| Integrity / authenticity | 128-bit GCM-SIV tag, header bound as AAD | forgery ≤ 2⁻¹²⁸ per message |
| Nonce-misuse resistance | SIV construction | a nonce collision reveals only plaintext equality |
| Key separation | HKDF-SHA256 with a fixed info label |
the AEAD key is never the raw master key |
| Password hardening | Argon2id v1.3, m = 64 MiB, t = 3, p = 4 | OWASP 2025 profile |
| Context binding | salt and purpose bound as AAD on wrapped keys | wrong salt or purpose fails authentication |
| Typed failures | one exception class per failing stage, fixed messages | no input echoed in an error |
| Secret hygiene | no key, secret, blob or plaintext in errors, logs or repr |
— |
All cryptographic primitives come from the cryptography
package. Nothing here is home-rolled crypto.
Out of scope (what it does NOT defend against)
- Weak passwords. Argon2id slows guessing down; it does not make a weak password strong.
- Replay. A valid blob stays valid. Detecting repeats is the caller's protocol concern.
- Traffic analysis. The header carries the plaintext length in the clear, and the blob size follows from it.
- Channel errors past the FEC capacity. The blob fails closed and never decrypts to
something wrong. With
NoFec, a single flipped bit is enough. - Memory disclosure. The crate wipes secrets from memory; Python's immutable
strandbytescannot promise that, and this package does not try. - Host compromise. A key in process memory is as safe as the process.
Operational contracts (the caller must honor)
- Use exactly 32 bytes of key. A shorter or longer key is an error, not something the package pads or truncates.
- Use a fresh salt per context, from
generate_salt(). The same password and salt give the same key. - Rate-limit
derive_keyfor untrusted callers. Each call costs 64 MiB and noticeable CPU. - Rekey before ~2⁴⁸ messages per key. That is the birthday bound for random 96-bit nonces.
- Decode with the same FEC strategy you encoded with. A blob from a default Rust vault
opens with
CryptoVault.default(); one from aNoFecvault needs aNoFecvault here. - Bound decrypt input from untrusted sources.
with_max_blob_len(cap)returns a vault that rejects larger blobs before any decryption work. With FEC this also bounds the decode time, which grows with the number of corrupted Viterbi sub-blocks. - One blob, one message. Framing and delivery belong to the caller.
Errors
Every failure is a CryptoVaultError. The subclass names the stage that failed, and str()
matches the crate's Display for the same input, word for word:
| Class | Raised for |
|---|---|
InvalidInputError |
wrong key, salt or seed length, empty password, interleave depth out of range, oversized input, malformed blob, unknown version |
EncodingError |
non-canonical base64, text that is not valid UTF-8 |
CipherError |
a tag that does not verify (wrong key, salt or purpose, any tampering) |
KeyDerivationError |
Argon2id failure, or no OS entropy for a salt or DEK |
ErrorCorrectionError |
corruption beyond the Reed-Solomon capacity, or no ChaCha20 in the backend for the CSPRNG layer |
Compatibility testing
The test suite replays reference data that the crate itself produced. For NoFec: its
constants, data blobs up to 128 KiB, wrapped keys, Argon2id outputs, and the exact error for
358 adversarial inputs (every single-bit flip of a blob, truncations, bad versions and
lengths, non-canonical base64, wrong keys and salts, invalid UTF-8). For the FEC layer: the
Reed-Solomon codewords and the outcome of 146 corrupted decodes, the interleaver
permutations (the CSPRNG layer included), Viterbi encodings and 26 corrupted decodes down to
the wrong bytes a heavily damaged stream decodes to, and 335 adversarial blobs under three
strategies (sampled bit flips, bursts, truncation, rewritten headers, wrong keys and salts).
The codec cases run against both the compiled and the Python codecs. In the other
direction, a second test hands the crate blobs made in Python under every strategy and checks
that it opens each one. The generator lives in interop/rust:
uv run pytest # full suite, Rust fixtures included
uv run pytest -m interop # Python → Rust; needs the Rust toolchain
cargo run --release --manifest-path interop/rust/Cargo.toml -- fixtures \
> tests/fixtures/rust_nofec.json # regenerate the NoFec fixtures
cargo run --release --manifest-path interop/rust/Cargo.toml -- fec-fixtures \
> tests/fixtures/rust_fec.json # regenerate the FEC fixtures
Requirements
- Python 3.11 or newer. Tested on Linux and Windows with 3.11 and 3.14. Compiled wheels exist for Linux (x86_64 and aarch64, glibc 2.17+ and musl), Windows x86_64 and macOS (universal2); any other platform gets the pure-Python wheel.
cryptography45.0.1 or newer, linked against OpenSSL 3.2 or newer (AES-GCM-SIV and Argon2id need it; the CSPRNG layer also uses its ChaCha20). Releases before 45.0.1 reject an empty plaintext, which the crate allows. The official wheels qualify.
Development
A Rust toolchain (1.96 or newer) is needed: uv sync builds the extension in native/ with
maturin, and rebuilds it whenever its sources change.
uv sync --locked
uv run ruff check . && uv run ruff format --check . && uv run mypy . && uv run pytest
uv run pip-audit
uv build
(cd native && cargo fmt --check && cargo clippy --release --all-targets -- -D warnings)
Roadmap
NoFecblob format, Argon2id and key wrapping. Implemented and cross-checked against the crate in both directions.- The FEC strategies. Reed-Solomon, the interleaver and Viterbi, so any
cryptovaultblob can be read from Python. Implemented and cross-checked in both directions. - Compiled codecs. A PyO3 extension wraps the
reedsolomonandviterbicrates the reference crate uses, with the pure-Python codecs as the fallback where no compiled wheel exists. Implemented; the crate fixtures check both.
License
Licensed under either of
- MIT license (LICENSE-MIT), or
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this package by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.
Copyright © 2026 Julian Bolivar / BolivarTech.
Metadata
Release files for cryptovault-py 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cryptovault_py-0.3.0.tar.gz | 37.9 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| cryptovault_py-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
| cryptovault_py-0.3.0-cp311-abi3-win_amd64.whl | CPython 3.11 | abi3 | Windows x86-64 | Details |
| cryptovault_py-0.3.0-cp311-abi3-musllinux_1_2_x86_64.whl | CPython 3.11 | abi3 | Linux musl 1.2+ x86-64 | Details |
| cryptovault_py-0.3.0-cp311-abi3-musllinux_1_2_aarch64.whl | CPython 3.11 | abi3 | Linux musl 1.2+ ARM64 | Details |
| cryptovault_py-0.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| cryptovault_py-0.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.11 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| cryptovault_py-0.3.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.11 | abi3 | macOS 10.12+ x86-64, macOS 11.0+ ARM64, macOS 10.12+ universal2 (ARM64, x86-64) | Details |
Total release size: 2.1 MB
Release files / cryptovault_py-0.3.0.tar.gz
| Download URL | cryptovault_py-0.3.0.tar.gz |
|---|---|
| Size | 37.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ab82df54ceecc4b98c3e7226cbf784363c7c6ad130561b2f129512f678217b5d
|
|
BLAKE2b-256 checksum How to use checksums |
38ba9e07865391ebf74376dd474e3e928c15b1012a9f9ce15d57f25b73d26d30
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-py3-none-any.whl
| Download URL | cryptovault_py-0.3.0-py3-none-any.whl |
|---|---|
| Size | 41.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd5c707c3772c3e775bdca45ed396c3cd3163a3d93269ee573e73f6c21c7bb66
|
|
BLAKE2b-256 checksum How to use checksums |
6d89969b31aaecfdd1012d3f2f4b9d1f1fa2bdbfb347d7b6c0c78b66f5fe20b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-cp311-abi3-win_amd64.whl
| Download URL | cryptovault_py-0.3.0-cp311-abi3-win_amd64.whl |
|---|---|
| Size | 155.0 kB |
| Tags | CPython 3.11 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1196a897d308201be0c6872320de4831e0a03b1d3b99a95495095d804af9900c
|
|
BLAKE2b-256 checksum How to use checksums |
17671397f8a3c1761d71529872e8e958504f8caa5e279b0eea98a2762677b35e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-cp311-abi3-musllinux_1_2_x86_64.whl
| Download URL | cryptovault_py-0.3.0-cp311-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 469.2 kB |
| Tags | CPython 3.11 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
effe45306896edec0b20dd9a258b2205b2cd8a2ebc3bba94add95831839da66f
|
|
BLAKE2b-256 checksum How to use checksums |
23da44f9fe9ad599d227d7f0c73e82eadface372f424e23425679e62fccf6eb0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-cp311-abi3-musllinux_1_2_aarch64.whl
| Download URL | cryptovault_py-0.3.0-cp311-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 435.6 kB |
| Tags | CPython 3.11 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
d918899892cdc6a34c1b88d8341889ba4f1b571443ea0c94358a2100b63742a6
|
|
BLAKE2b-256 checksum How to use checksums |
58854e0129d9493c03852f26cad6cfde3b534998979cee8dbcacbd0a5f4b343d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | cryptovault_py-0.3.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 257.8 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
a55bc97bc0d8f6c03d829424fe18a828c5ab5f73afee5e30ee7668cc6c213058
|
|
BLAKE2b-256 checksum How to use checksums |
c4a999c4a0d3510ce1603b71fcff8a853a0e8c38a4dada6b7d50e2a6450c3678
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | cryptovault_py-0.3.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 256.9 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
6c5fcdb3d476ca14ad3c10f89e9af909525859832f4cb49c737455d15fe34f9b
|
|
BLAKE2b-256 checksum How to use checksums |
9b3eed3894e0f6ac62ab1df5c450d5988718b20596aec77a141a4233483e0099
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / cryptovault_py-0.3.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | cryptovault_py-0.3.0-cp311-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 452.1 kB |
| Tags | CPython 3.11 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
139b9652924ac98eda4aa2baa82594cb209e55d037f40c6a675cea6f64835b07
|
|
BLAKE2b-256 checksum How to use checksums |
eccb6d45b7a5548f9978269eb8305bc5dd0816c8ff8e9d4b894fb464b2f45886
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log