Typed, fixed-size multi-secret containers for Python.
PyPI · TestPyPI · Source · Documentation · Security
Keyprism stores several plaintexts in one fixed-size container. Each passphrase opens one plaintext. The file format gives encrypted slots and random-filled slots the same size and byte-level structure.
Applications use the typed Container API. Keyprism selects salts, nonces,
padding, and physical slot positions inside the library.
Security status: Keyprism 0.1.0 has automated cryptographic, statistical, and misuse tests. No external cryptographer has audited the design or implementation. Do not use this pre-1.0 release for high-risk production data.
| Capability | Implementation |
|---|---|
| Multiple plaintexts | Each passphrase protects one complete plaintext |
| Fixed-size storage | Each physical slot uses the configured capacity |
| Random-filled slots | Empty slots use secrets.token_bytes() |
| Length hiding | Encryption covers the length field, plaintext, and random padding |
| Password hardening | Argon2id derives a separate key for each real slot |
| Authenticated encryption | ChaCha20-Poly1305 authenticates each real slot |
| Restricted API | Callers cannot set nonces, salts, offsets, or slot indexes |
| Typed package | Type annotations and py.typed support PEP 561 tooling |
Keyprism requires Python 3.11 or newer.
python -m pip install keyprismRelease indexes:
| Index | URL | Purpose |
|---|---|---|
| PyPI | pypi.org/project/keyprism | Stable releases |
| TestPyPI | test.pypi.org/project/keyprism | Release verification |
| Topic | Reference |
|---|---|
| First container | Quick start |
| Classes, methods, and properties | API reference |
| Serialized bytes | Container format |
| Algorithms and parameters | Cryptographic profile |
| Claims and exclusions | Security model |
| Package exceptions | Error handling |
| Python and format support | Compatibility |
from pathlib import Path
from keyprism import Container
path = Path("private-notes.kpr")
container = Container.create(num_slots=4, slot_capacity=4096)
container.write_slot("ordinary-passphrase", b"Buy milk")
container.write_slot("private-passphrase", b"Recovery instructions")
container.save(path)
loaded = Container.load(path)
assert loaded.open("ordinary-passphrase") == b"Buy milk"
assert loaded.open("private-passphrase") == b"Recovery instructions"
assert loaded.open("unknown-passphrase") is Nonesave() fills unwritten slots with CSPRNG output before writing the file.
open() attempts each physical slot and returns matching plaintext bytes.
Wrong passphrases return None.
Keyprism accepts plaintext as bytes. Your application controls text
encoding, object serialization, user authentication, and secret input.
from keyprism import Container
container = Container.create(
num_slots=4,
slot_capacity=4096,
)
loaded = Container.load(path)| Operation | Result |
|---|---|
Container.create(num_slots, slot_capacity) |
New writable in-memory container |
Container.load(path) |
Validated read-only container |
The slot capacity includes salt, nonce, authentication tag, and encrypted length framing. A slot needs at least 48 bytes.
| Property | Type | Meaning |
|---|---|---|
num_slots |
int |
Observable physical slot count |
slot_capacity |
int |
Serialized bytes per physical slot |
max_plaintext_size |
int |
Maximum plaintext bytes per slot |
serialized_size |
int |
Exact saved container size |
finalized |
bool |
Whether each slot contains serialized bytes |
| Method | Behavior |
|---|---|
write_slot(passphrase, plaintext) |
Encrypts plaintext into a random unwritten slot |
open(passphrase) |
Returns matching plaintext bytes or None |
finalize() |
Fills unwritten slots and prevents later writes |
save(path) |
Finalizes and writes the container |
Loaded containers reject write_slot(). The format stores no unused-slot
marker, so the library cannot select a safe overwrite target after loading.
Create a new container when you need to change its plaintext set.
Keyprism format version 1 starts with an eight-byte header.
| Offset | Size | Field |
|---|---|---|
| 0 | 4 bytes | Magic and format version: KPR\x01 |
| 4 | 4 bytes | Big-endian slot capacity |
| 8 | Remaining bytes | One or more fixed-size slots |
The file length determines the physical slot count:
physical_slot_count = (file_size - 8) / slot_capacity
serialized_file_size = 8 + (physical_slot_count * slot_capacity)
| Offset | Size | Real slot | Empty slot |
|---|---|---|---|
| 0 | 16 bytes | Argon2id salt | CSPRNG bytes |
| 16 | 12 bytes | ChaCha20-Poly1305 nonce | CSPRNG bytes |
| 28 | slot_capacity - 44 bytes |
Ciphertext | CSPRNG bytes |
slot_capacity - 16 |
16 bytes | Poly1305 tag | CSPRNG bytes |
The authenticated plaintext contains a four-byte big-endian length, the plaintext, and random padding. The maximum plaintext size is:
max_plaintext_size = slot_capacity - 48
Format version 1 fixes these parameters:
| Component | Selection |
|---|---|
| KDF | Argon2id version 1.3 |
| Time cost | 3 iterations |
| Memory cost | 131,072 KiB |
| Parallelism | 1 lane |
| Derived key | 32 bytes |
| Salt | 16 random bytes per real slot |
| AEAD | ChaCha20-Poly1305 |
| Nonce | 12 random bytes per real slot |
| Authentication tag | 16 bytes |
| Random source | Python secrets module |
AEAD associated data binds each slot to the format version and slot capacity. Changing the KDF parameters for format version 1 would make existing containers unreadable.
Keyprism targets an adversary who obtains a finalized container and may know one passphrase. The design aims for computational indistinguishability between a real encrypted slot and a random-filled slot under the security of Argon2id, ChaCha20-Poly1305, and the operating system random source.
The format reveals:
| Observable value | Source |
|---|---|
| File type marker | Four-byte header magic |
| Slot capacity | Header |
| Physical slot count | File length |
| Total file size | Filesystem |
| Access and transport metadata | Host application and operating system |
The format does not store real-slot flags, plaintext lengths outside ciphertext, timestamps, or a real-plaintext count.
Keyprism does not protect against a compromised host, live process inspection,
keylogging, weak passphrases, external evidence about plaintext count, file
deletion, or rollback. Python cannot provide cycle-level constant-time
execution. open() performs the same KDF and authentication work for each
physical slot to avoid work that depends on the matching slot position.
Use strong unique passphrases. Keep plaintext out of logs, telemetry, shell history, swap, and crash reports. Treat finalized containers as opaque binary files.
Report security defects through a private security channel. Do not publish passphrases, plaintext, derived keys, production containers, or exploit details in a public issue.
All package exceptions inherit from KeyPrismError.
| Exception | Condition |
|---|---|
ConfigurationError |
Invalid container dimensions |
ContainerFormatError |
Malformed or unsupported serialized framing |
ContainerStateError |
Operation conflicts with container state |
InvalidPassphraseError |
Empty or invalid passphrase input |
NoAvailableSlotError |
Writable slots are exhausted |
PaddingError |
Authenticated payload framing is invalid |
PlaintextTooLargeError |
Plaintext exceeds max_plaintext_size |
Exception messages omit passphrases, plaintext, keys, and raw slot bytes.
from keyprism import Container, PlaintextTooLargeError
container = Container.create(slot_capacity=128)
try:
container.write_slot("passphrase", b"x" * 100)
except PlaintextTooLargeError:
pass- Keyprism supports Python 3.11, 3.12, and 3.13.
- Keyprism 0.1.0 uses container format version 1.
- The earlier Tkinter prototype and unpublished package formats are incompatible with Keyprism format version 1.
- Pre-1.0 releases may change the API or file format. The changelog records compatibility changes.
Use GitHub Issues for bug reports and API questions. Include a minimal reproducible example without passphrases, plaintext, keys, or production containers.
Report security defects through the private process in SECURITY.md. Do not publish exploit details in a public issue.
Keyprism uses the MIT License. The source distribution and wheel include the license text.