Spec version: v0 (initial)
For a quick start and project overview, see Home. For the CLI specification, see cli.html.
An Oboron format represents the full transformation of the plaintext to the encrypted text (obtext), including:
Formats combine a scheme (cryptographic algorithm) with an encoding (string representation):
aasv)c32)aasv.c32)Given an encryption key, the format thus uniquely specifies the complete transformation from a plaintext string to an encoded obtext string.
Formats are represented by identifiers:
ob:{scheme}.{encoding}, (URI-like
syntax, e.g.,
ob:aasv.c32),{scheme}.{encoding}, when the
context is clearAPI Notes:
ob: namespace prefix is NOT
used in the oboron API. Formats
like aasv.c32 MUST be used
directly.enc/dec names for
methods and functions. The enc
operation comprises the full process, including
the encryption and encoding stages.b32 - standard base32: Balanced
compactness and readability, uppercase
alphanumeric (RFC 4648 Section 6)c32 - Crockford base32: Balanced
compactness and readability, lowercase
alphanumeric; designed to avoid accidental
obscenityb64 - standard URL-safe base64:
Most compact, case-sensitive, includes
- and _ characters
(RFC 4648 Section 5)hex - hexadecimal: Slightly faster
performance (~2-3%), longest outputFAQ: Why use Crockford's base32 instead of the RFC standard one?
Crockford's base32 alphabet minimizes the probability of accidental obscenity words, which is important when using with short prefixes: Whereas accidental obscenity is not an issue when working with full encrypted outputs (as any such words would be buried as substrings of a 28+ character long obtext), it may become a concern when using short prefixes as references or quasi-hash identifiers.
Schemes define the encryption algorithm and its properties, classified into tiers:
a -
Authenticated
ob:aasv,
ob:aags,
ob:apsv,
ob:apgsa-tier schemes for
security-critical applicationsu -
Unauthenticated
ob:upbcz - Obfuscation
tier
ob:zrbcx -
deterministic obfuscation with constant
IVThe second letter of the scheme ID further describes the properties of the scheme:
.a.. - avalanche,
deterministic
ob:aasv,
ob:aags.p.. -
probabilistic
ob:apsv,
ob:apgs,
ob:upbcThe remaining two letters in scheme IDs indicate the algorithm:
gs = AES-GCM-SIVsv = AES-SIVbc = AES-CBC| Scheme | Algorithm | Deterministic? | Authenticated? | Notes |
|---|---|---|---|---|
ob:aasv |
AES-SIV | Yes | Yes | General purpose, deterministic |
ob:aags |
AES-GCM-SIV | Yes | Yes | Deterministic alternative |
ob:apsv |
AES-SIV | No | Yes | Maximum privacy protection |
ob:apgs |
AES-GCM-SIV | No | Yes | Probabilistic alternative |
ob:upbc |
AES-CBC | No | No | Unauthenticated - use with caution |
Key Concepts:
ob:aasv: General-purpose secure
encryption with deterministic output and
compact sizeob:apsv: Maximum privacy with
probabilistic output (larger size due to
nonce)ob:upbc: Only when integrity is
handled externallyNote on encryption strength: All
a-tier andu-tier schemes MUST use 256-bit AES encryption. Thez-tier uses 128-bit AES for performance in non-security contexts.
Oboron combines encryption and encoding in a single operation, requiring specific terminology:
enc operation (encryption +
encoding), distinct from cryptographic
ciphertextThe cryptographic ciphertext (bytes, not string) is an internal implementation detail, NOT exposed in the public API.
The high-level process flow is:
enc operation:
[plaintext] (string)
-> encryption
-> [ciphertext] (bytes)
-> encoding
-> [obtext] (string)
dec operation:
[obtext] (string)
-> decoding
-> [ciphertext] (bytes)
-> decryption
-> [plaintext] (string)
The above diagram is conceptual; actual
implementation includes scheme-specific steps like
scheme byte appending and (for z-tier
schemes only) optional ciphertext prefix
restructuring. With this middle-step included, the
diagram becomes:
enc operation:
[plaintext]
-> encryption
-> [ciphertext]
-> oboron pack
-> [payload]
-> encoding
-> [obtext]
dec operation:
[obtext]
-> decoding
-> [payload]
-> oboron unpack
-> [ciphertext]
-> decryption
-> [plaintext]
In a-tier and u-tier
schemes, the difference between the payload and
the ciphertext is in the 2-byte scheme marker that
is appended to the ciphertext, enabling scheme
autodetection in decoding.
Oboron's CBC schemes use a custom padding scheme optimized for UTF-8 strings:
Rationale: Oboron is defined to
operate exclusively on UTF-8 strings, not
arbitrary binary data. This is a protocol-level
requirement: all enc operations MUST
accept a UTF-8 string input and all
dec operations MUST return a UTF-8
string. The 0x01 padding byte can never appear in
valid UTF-8 input, ensuring unambiguous decoding.
Under the UTF-8 input constraint, this padding is
functionally equivalent to PKCS#7 and does not
weaken security. Implementations MUST enforce the
UTF-8 constraint, eliminating padding ambiguity
errors at runtime.
Oboron uses a single 512-bit master key partitioned into algorithm-specific subkeys:
ob:aags, ob:apgs:
use the first 32 bytes (256 bits) for
AES-GCM-SIV keyob:aasv, ob:apsv:
use the full 64 bytes (512 bits) for AES-SIV
keyob:upbc uses the last 32 bytes
(256 bits) for AES-CBC keyDesign Rationale: This approach prioritizes low latency for short-string encryption. No hash-based KDF (e.g., HKDF) is used, as this would dominate runtime for intended workloads.
The master key MUST NOT leave the application. Algorithm-specific keys MUST be extracted on-the-fly and MUST NOT be cached or stored.
FAQ: Why use a single key across all schemes?
- Simplifies deployment: Store one key instead of multiple
- Reduces errors: No risk of mismatching keys to algorithms
The default key input format is base64. This is consistent with Oboron's strings-first API design. As any production use will typically read the key from an environment variable, this allows the string format to be directly fed into the constructor.
The base64 format was chosen for its compactness, as an 86-character base64 key is easier to handle manually (in secrets or environment variables management UI) than a 128-character hex key.
While any 512-bit key is accepted by Oboron, the keys generated by Oboron's key generation utilities MUST NOT include any dashes or underscores, in order to ensure the keys are double-click selectable, and to avoid any human visual parsing confusion due to underscores.
Important technical detail: Not every 86-character base64 string is a valid 512-bit key. Since 512 bits requires 85.3 bytes when base64-encoded, the final character is constrained by padding requirements. When generating keys, implementations MUST use one of the following methods:
Implementations SHOULD support the following key input formats in addition to the default base64 format:
If you've used Git, you're already familiar with
prefix entropy: you can reference commits with
just the first 7 characters of their SHA1 hash
(like git show a1b2c3d). This works
because cryptographic hashes distribute entropy
evenly across all characters.
Oboron schemes exhibit similar prefix quality. Consider these comparisons:
Short Reference Strength:
Collision Resistance:
For a 1-in-a-million chance of two items sharing the same prefix:
(These estimates assume uniform ciphertext distribution under a fixed key.)
Practical Implications:
In a system with 1,000 unique items using 7-character Oboron prefixes:
This enables Git-like workflows for moderate-scale systems: database IDs, URL slugs, or commit references that are both human-friendly and cryptographically robust for everyday use cases.
Comparing the prefix collision resistance in the previous section, Oboron and standard hashing algorithms were compared against each other. But when we consider the full output, then they are not on the same plane: while SHA1 and SHA256 collision probabilities are astronomically small, they are never zero, and the birthday paradox risk can become a factor in large systems even with the full hash. Oboron, on the other hand, is a symmetric encryption protocol, and as such it is collision free (although applying this label to an encryption protocol is awkward): for a fixed key and within the block-cipher domain limits, Oboron is injective (one-to-one), i.e. two different inputs can never result in the same output.
Oboron is optimized for performance with short strings, often exceeding both SHA256 and JWT performance while providing reversible encryption.
Note: As a general-purpose encryption protocol, Oboron is not a replacement for either JWT or SHA256. We use those two for baseline comparison, as they are both standard and highly optimized libraries.
Note: Benchmark data is from the Rust reference implementation.
| Scheme | 8B Encode | 8B Decode | Security | Use Case |
|---|---|---|---|---|
ob:aasv |
334 ns | 364 ns | Secure + Auth | Balanced performance + security |
| JWT | 550 ns | 846 ns | Auth only* | Signature without encryption |
| SHA256 | 191 ns | N/A | One-way | Hashing only |
* Note: JWT baseline
(HMAC-SHA256) provides authentication without
encryption. Despite comparing against our stronger
a-tier (secure +
authenticated), Oboron maintains performance
advantages while providing full confidentiality.
Performance advantages:
| Method | Small string output length |
|---|---|
ob:aasv |
31-48 characters |
ob:apsv |
56-74 characters |
| SHA256 | 64 characters |
| JWT | 150+ characters |
A more complete output length comparison is given in the Appendix.
All Oboron implementations MUST provide the following abstract interface.
enc(plaintext: string) → obtext:
string — Encrypts and encodes
the plaintext using the configured format;
returns an obtext stringdec(obtext: string) → plaintext:
string — Decodes and decrypts
the obtext; returns the original
plaintextautodec(obtext: string) →
plaintext: string — Decodes
obtext in any supported format encrypted with
the same key, without needing to know the
format in advanceA codec MUST be constructible from a key and a format specifier. Implementations MUST support construction from a base64 key string (primary interface), as well as from a hex key string or raw key bytes.
Rust:
use oboron::AasvC32;
let ob = AasvC32::new(
&env::var("OBORON_KEY")?
)?;
Python:
from oboron import AasvC32
import os
ob = AasvC32(os.getenv("OBORON_KEY"))
All implementations MUST provide a key generation utility that produces a valid 512-bit key encoded as a base64 string (86 characters, URL-safe alphabet, no padding characters).
Rust:
let key = oboron::generate_key();
cargo run --bin keygen
Python:
key = oboron.generate_key()
python -m oboron.keygen
Oboron implementations MUST maintain full cross-language compatibility:
All implementations MUST pass the common test vectors.
mock1 is a non-cryptographic scheme
used for testing, whose ciphertext is equal to the
plaintext bytes (identity transformation). It is
included in the tables below as baseline.
(Note: mock1 is a non-production
scheme for testing purposes only)
| Format | 4B | 8B | 12B | 16B | 24B | 32B | 64B | 128B |
|---|---|---|---|---|---|---|---|---|
| mock1.b32 | 10 | 16 | 23 | 29 | 42 | 55 | 106 | 208 |
| aags.b32 | 36 | 42 | 48 | 55 | 68 | 80 | 132 | 234 |
| aasv.b32 | 36 | 42 | 48 | 55 | 68 | 80 | 132 | 234 |
| apgs.b32 | 55 | 61 | 68 | 74 | 87 | 100 | 151 | 253 |
| apsv.b32 | 61 | 68 | 74 | 80 | 93 | 106 | 157 | 260 |
| upbc.b32 | 55 | 55 | 55 | 55 | 80 | 80 | 132 | 234 |
| zrbcx.b32 | 29 | 29 | 29 | 29 | 55 | 55 | 106 | 208 |
| Format | 4B | 8B | 12B | 16B | 24B | 32B | 64B | 128B |
|---|---|---|---|---|---|---|---|---|
| mock1.b64 | 8 | 14 | 19 | 24 | 35 | 46 | 88 | 174 |
| aags.b64 | 30 | 35 | 40 | 46 | 56 | 67 | 110 | 195 |
| aasv.b64 | 30 | 35 | 40 | 46 | 56 | 67 | 110 | 195 |
| upbc.b64 | 46 | 46 | 46 | 46 | 67 | 67 | 110 | 195 |
| apgs.b64 | 46 | 51 | 56 | 62 | 72 | 83 | 126 | 211 |
| apsv.b64 | 51 | 56 | 62 | 67 | 78 | 88 | 131 | 216 |
| zrbcx.b64 | 24 | 24 | 24 | 24 | 46 | 46 | 88 | 174 |
| Format | 4B | 8B | 12B | 16B | 24B | 32B | 64B | 128B |
|---|---|---|---|---|---|---|---|---|
| mock1.hex | 12 | 20 | 28 | 36 | 52 | 68 | 132 | 260 |
| aags.hex | 44 | 52 | 60 | 68 | 84 | 100 | 164 | 292 |
| aasv.hex | 44 | 52 | 60 | 68 | 84 | 100 | 164 | 292 |
| upbc.hex | 68 | 68 | 68 | 68 | 100 | 100 | 164 | 292 |
| apgs.hex | 68 | 76 | 84 | 92 | 108 | 124 | 188 | 316 |
| apsv.hex | 76 | 84 | 92 | 100 | 116 | 132 | 196 | 324 |
| zrbcx.hex | 36 | 36 | 36 | 36 | 68 | 68 | 132 | 260 |