| Version | Supported |
|---|---|
| 0.3.x | ✅ |
| 0.2.x | ❌ (EOL) |
| 0.1.x | ❌ (EOL) |
0.3.0 is an additive release (new TwofishXTS disk-volume construct and
the ctr_width keyword; no breaking changes — see the release
notes). 0.2.0 was a
breaking-change release (the old per-mode class API is removed; see the
release notes for the migration table). 0.2.x and 0.1.x receive no
further updates, including security fixes — upgrade to 0.3.x.
Please report security vulnerabilities by emailing the maintainers directly rather than opening a public issue. We will acknowledge receipt within 48 hours.
Key and IV material is cleared from memory on drop via the zeroize
crate (enabled on twofish, cipher, and every RustCrypto mode crate
this library uses), which prevents the compiler from optimizing the clear
away. Two lifetime models apply, depending on what's holding the
material:
TwofishKeyholds a reference-countedArc<Twofish>schedule. ECB sessions (ecb_encryptor/ecb_decryptor) borrow that sameArcrather than cloning it. The schedule is zeroized once, on the last drop of theArc— i.e. once the key and every ECB session derived from it have all been dropped.- Every non-ECB session (CBC/CTR/CFB/OFB, from
encryptor/decryptor, and the internal sessions backing one-shotencrypt/decrypt) owns an independent clone of the key schedule (184 bytes of expanded round keys — never a re-derivation from the raw key) and zeroizes that clone on its own drop, independently of the parentTwofishKeyand of any other session.
Raw key bytes passed to TwofishKey() are copied into the expanded
schedule once, at construction, and never copied again afterward.
What's zeroized beyond the key schedule: the engine's own transient
plaintext copies are cleared too, not just key material. The sub-block
residue buffer (Pending, which holds not-yet-transformed input across
update() calls) and the CBC/ECB padded-decrypt holdback block
(HeldBlock, which holds one decrypted block awaiting padding
resolution) are both zeroized on overwrite and on drop.
Caveat inherent to zeroize generally: drop-based zeroization clears
the copies this library owns and knows about. It cannot retroactively
erase transient stack copies the compiler's optimizer may have
introduced during moves earlier in a value's lifetime (registers,
spills) — this is a property of how Rust (and zeroize) work, not
specific to this crate — nor can it reach copies that end up in
Python-managed memory (see below).
Note: Python's garbage collector controls when objects are dropped.
For sensitive applications, keep TwofishKey/TwofishSession scope
narrow and avoid storing keys in long-lived variables.
Python-side key bytes: TwofishKey.__new__ coerces its key
argument through bytes(key) before construction, and the caller's own
key object (however it was built) is a normal Python object too — both
are immutable and outside this library's reach, so they persist in
Python-managed memory until garbage collected and the allocator reuses
that memory. This is standard for Python crypto bindings — the
cryptography/PyCA library has the same caveat — and matters mainly if
your threat model includes memory disclosure; scope key-bearing
variables narrowly if it does.
TwofishXTS (RFC 0003) zeroizes in full, not just its two key
schedules. Both expanded Twofish schedules (the data-key half and the
tweak-key half) are zeroized on drop, matching TwofishKey's model above.
In addition — because XTS has no engine::Session to hide transient
buffers behind — the owned copy of each call's input buffer (made while
the GIL is held, see "Concurrency" below) and the engine's returned output
buffer are explicitly zeroized once they've been copied out to the Python
bytes object the caller receives; the ciphertext-stealing scratch used
internally for non-block-multiple data units is zeroized the same way,
inside the Rust XTS engine itself. The same Python-side-key-bytes caveat
above applies to TwofishXTS's key argument, with one addition: the
facade's _split_xts_key splits the coerced key into key1/key2
halves, each a distinct, immutable bytes object outside this library's
reach and subject to the same limitation as the original key argument.
Unlike the rest of this library — which binds RustCrypto's audited
twofish/cbc/ctr/cfb-mode/ofb crates and adds no cipher-mode code
of its own — TwofishXTS's XTS-mode logic is oxifish's own
implementation, not RustCrypto's: RustCrypto has no XTS crate (its
block-mode traits don't fit ciphertext stealing's whole-data-unit shape).
The tweak-block encoding, GF(2¹²⁸) doubling, and ciphertext stealing
(src/xts.rs, ~100 lines) are hand-written against the cipher 0.5
traits. This is the one seam where this project's usual "we bind audited
implementations, we don't write cipher code" pitch does not hold, and it's
said plainly here rather than left implicit.
That implementation is validated four independent ways: the official IEEE
1619 (P1619/D16) Annex B known-answer vector set, generically instantiated
with AES in cargo tests (including the ciphertext-stealing vectors, not
just the block-aligned ones); bidirectional cross-validation against the
independent xts-mode crate (a cargo dev-dependency oracle, run both
encrypt-ours/decrypt-theirs and the reverse); an independent pure-Python
XTS reference implementation in the pytest suite, built over this
library's own KAT-pinned ECB primitive; and a real VeraCrypt volume
(tests/fixtures/, when present — see that directory's README for the
recipe and current status).
- Not constant-time: Twofish uses key-dependent S-boxes, which are
inherently non-constant-time. This is suitable for local file
decryption (KeePass databases) but not for server-side encryption where
timing attacks are feasible.
DecryptionErrorcloses the error-string side channel (one fixed message for every padded-decrypt failure) but makes no constant-time claim beyond that — see the library's docstrings for the exact scope. - No authentication (AEAD): this library implements unauthenticated
encryption only. Callers needing integrity must use an outer MAC (as
KDBX does). Wrong key, wrong IV, or wrong mode on unpadded data is not
detected and silently yields garbage plaintext. If an attacker can
submit chosen ciphertexts to a decryption endpoint and observe whether
DecryptionErroris raised, that accept/reject signal is a padding oracle (a Vaudenay-style attack on CBC padding) — the message-uniformity work above reduces incidental leakage but is not a defense against an online oracle. The only real defense is encrypt-then-MAC with the MAC verified before any decryption is attempted; never decrypt first and check second. - IV/counter reuse is caller-catastrophic: reusing an
ivunder the same key is not detected or prevented by this library. For CTR and OFB, reuse repeats the keystream, letting an attacker recover the XOR of two plaintexts from the XOR of the corresponding ciphertexts; CFB carries the same risk up to the first differing block. For CBC, IV reuse leaks whether two messages share a common prefix. Omitivonencrypt/encryptorand let the CSPRNG-backed auto-generated IV do the right thing by default; never pass the same IV to two encryptions under one key. - ECB mode: provided for compatibility and KAT/interop use only, via
the dedicated
ecb_encryptor/ecb_decryptorfactories. It leaks equal-block plaintext patterns at any length — the factories restrict discoverability, not payload size, so multi-block ECB use is on the caller. This is also the documented migration path off the dead PyPItwofishpackage (see the README), which was itself a bare single-block primitive with the same pattern-leakage exposure for any caller who processes more than one block through it. Use CBC, CTR, CFB, or OFB for actual encryption. - XTS (
TwofishXTS): tweak reuse across rewrites of the same data unit is correct here, unlike IV reuse everywhere else in this library — see the README's "tweak is a position, not a nonce" note. A wrong tweak, or a data-unit size that doesn't match the format being decoded (e.g. assuming a drive's physical 4Kn sector size instead of VeraCrypt's fixed 512-byte data unit), silently yields garbage plaintext with no error, the same failure mode as a wrong key or IV elsewhere in this library. Like every other construct here, XTS is unauthenticated — it provides no integrity guarantee, only confidentiality.
For details on Twofish's security properties, see the RustCrypto twofish crate.
Wheels are built via GitHub Actions and published to PyPI using OIDC trusted publishing (no stored API tokens).