obcrypt
The bytes-in / bytes-out cryptographic core of the oboron protocol.
obcrypt implements oboron's authenticated core encryption schemes
operating on raw byte slices. It does not perform any text
encoding (no base64, no base32, no hex) and does not validate
UTF-8 — plaintext bytes pass through unchanged.
For the full string-in / string-out oboron protocol (with obtext
encoding and format strings), use the
oboron crate, which depends
on this one. The unauthenticated and obfuscation schemes live in the
separate obu layer, not here.
Keys do have a canonical text form: hex (128 lowercase
characters). Key::from_hex / Key::to_hex handle that. obcrypt
intentionally does not support other key encodings (base64, base32,
…) — in cryptography clarity beats compactness, and the size saving
of base64 over hex (86 vs 128 chars for a 64-byte key) isn't enough
to justify the visual noise.
When to use which
obcrypt |
oboron |
|
|---|---|---|
| Input / output | &[u8] / Vec<u8> |
&str / String |
| Encoding | none | base64 / base32 / hex |
| UTF-8 validation | no | yes |
| Intended use | binary contexts, embedded, low-level integration | text contexts, identifiers, URLs |
Quick start
use ;
let key = random;
let output = encrypt?;
let plaintext = decrypt?;
assert_eq!;
# Ok::
The output carries no scheme marker — the same Scheme used to
encrypt must be supplied to decrypt. Supplying the wrong scheme
fails the authentication check rather than returning garbage.
Schemes
A scheme identifier is <property><algorithm>: the first letter is
d (deterministic) or p (probabilistic); the rest names the AEAD
(siv = AES-SIV, gcmsiv = AES-GCM-SIV). All four are authenticated.
| Properties | Algorithm | Scheme |
|---|---|---|
| deterministic | AES-SIV | dsiv |
| probabilistic | AES-SIV | psiv |
| deterministic | AES-GCM-SIV | dgcmsiv |
| probabilistic | AES-GCM-SIV | pgcmsiv |
Scheme decision matrix:
- Need same-plaintext-same-output? (e.g. for stable IDs or
encrypted lookups.) Use a deterministic variant (
dsiv,dgcmsiv). Otherwise use a probabilistic variant (psiv,pgcmsiv), which draws a fresh nonce per call. - Want broad nonce-misuse resistance with a clean security
story? Prefer the SIV variants (
dsiv,psiv) —dsivis the most general default. - Want smallest footprint / fastest on AES-NI hardware? Prefer
the GCM-SIV variants (
dgcmsiv,pgcmsiv). SIV typically wins on short inputs; GCM-SIV scales better and pulls ahead on medium-to-large inputs (crossover around 256 bytes in practice).
Plus testing-only schemes behind the mock feature flag — mock1
(identity) and mock2 (reverse). They perform no encryption
and exist solely for round-tripping unit tests, layering benchmarks,
and as inert fallbacks. Never enable mock in a production build.
See SECURITY.md for the full threat model and
algorithm justification.
Output format
The output of encrypt is exactly the scheme's AEAD output — there
is no scheme marker:
- deterministic:
siv-tag || ciphertext(dsiv) orciphertext || tag(dgcmsiv). - probabilistic: a fresh nonce is prepended.
The scheme is part of the caller's context on both sides; obcrypt follows oboron's no-marker model, where decrypting under the wrong scheme fails the authentication check.
API
Two parallel forms are provided for every operation:
- Owned: returns a fresh
Vec<u8>. Convenient. _into: appends to a caller-provided&mut Vec<u8>. Lets integrators (notablyoboron) avoid an intermediate buffer allocation when piping output to a downstream encoder.
;
;
;
;
Raw per-scheme primitives live under
obcrypt::schemes::{dsiv, psiv, dgcmsiv, pgcmsiv, ...} for callers
that already know the scheme statically and want to skip the enum
dispatch. Each scheme module exposes the same four functions:
encrypt, encrypt_into, decrypt, decrypt_into.
Key material
A single 64-byte master key serves every scheme:
dsiv/psivuse the full 64 bytes directly as the AES-SIV key.dgcmsiv/pgcmsivderive a 32-byte AES-256-GCM-SIV key from the master withHKDF-Expand(HMAC-SHA-256, infogcmsivshared by both GCM-SIV schemes; the Extract step is skipped, as the master is already a uniform pseudorandom key).
Key is ZeroizeOnDrop and redacts its Debug output.
Performance
obcrypt is designed for low-overhead embedding in performance-
sensitive paths (the oboron crate uses it on every enc /
dec call). Notable choices:
- The owned
encrypt/decryptcalls the underlying AEAD's exact-capacity allocator, avoiding intermediate buffers. - The
_intoform writes ciphertext directly into the caller's buffer viaaead::encrypt_in_place, with a privateTailBufferadapter to scope the in-place region — zero extra allocations. - All public functions are
#[inline]. Combined with workspace- level LTO (which the parent workspaceCargo.tomlenables), the cross-crate boundary collapses on the hot path.
Cargo features
See FEATURES.md for the full matrix. By default
every production scheme (dgcmsiv, pgcmsiv, dsiv, psiv) is
enabled; schemes are individually gated so binary size scales with
what you actually use.
Versioning
obcrypt follows semver from 1.0. See CHANGELOG.md
for release notes. The scheme output formats are bound to the oboron
protocol spec: output produced by any obcrypt 1.x build decrypts
under any other 1.x build with the matching scheme feature enabled.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.