1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! Bytes-in / bytes-out cryptographic core of the
//! [oboron](https://oboron.org/) protocol.
//!
//! `obcrypt` implements oboron's authenticated core encryption schemes
//! operating on raw byte slices. It does *not* encode the output (no
//! base64, no base32) and does *not* validate UTF-8 — plaintext bytes
//! pass through unchanged.
//!
//! Keys do have a canonical text form: **hex** (128 lowercase
//! characters). [`Key::from_hex`] / [`Key::to_hex`] handle that — see
//! [`Key`] for rationale.
//!
//! For the full string-in / string-out oboron protocol — with obtext
//! encoding and format strings — see the
//! [`oboron`](https://gitlab.com/oboron/oboron-rs) crate, which depends
//! on this one. The unauthenticated and obfuscation schemes live in the
//! separate obu layer.
//!
//! # Quick start
//!
//! ```
//! # #[cfg(feature = "dsiv")] {
//! use obcrypt::{encrypt, decrypt, Key, Scheme};
//!
//! let key = Key::random();
//! let ct = encrypt(b"secret data", Scheme::Dsiv, &key)?;
//! let pt = decrypt(&ct, Scheme::Dsiv, &key)?;
//! assert_eq!(pt, b"secret data");
//! # }
//! # Ok::<(), obcrypt::Error>(())
//! ```
//!
//! # Schemes
//!
//! | Scheme | Properties | Algorithm | Key material |
//! |--------|------------|-----------|--------------|
//! | [`Scheme::Dsiv`] | deterministic | AES-SIV | full 64-byte master |
//! | [`Scheme::Dgcmsiv`] | deterministic | AES-GCM-SIV | HKDF-derived |
//! | [`Scheme::Psiv`] | probabilistic | AES-SIV | full 64-byte master |
//! | [`Scheme::Pgcmsiv`] | probabilistic | AES-GCM-SIV | HKDF-derived |
//!
//! All four are authenticated: `decrypt` returns
//! [`Error::DecryptionFailed`] on tampering, a wrong key, or the wrong
//! scheme. Deterministic variants (`dsiv`, `dgcmsiv`) leak plaintext
//! equality (same plaintext + key → same output); use a probabilistic
//! variant when that isn't acceptable.
//!
//! Plus testing-only schemes behind the `mock` feature flag — `mock1`
//! (identity) and `mock2` (reverse), which perform **no encryption**
//! and are not selectable through `Scheme::from_str`.
//!
//! # Output format
//!
//! The output is exactly the scheme's AEAD output — there is no scheme
//! marker. The scheme is supplied by the caller to both [`encrypt`] and
//! [`decrypt`] (oboron's no-marker model: supplying the wrong scheme
//! fails the authentication check). Per-scheme byte layouts:
//!
//! - deterministic: `siv-tag || ciphertext` (`dsiv`) or
//! `ciphertext || tag` (`dgcmsiv`).
//! - probabilistic: a fresh nonce is prepended.
//!
//! # API
//!
//! Each operation has an **owned** form (returns a fresh `Vec<u8>`) and
//! an **`_into`** form (appends to a caller buffer; zero extra
//! allocation on the AEAD path):
//!
//! - [`encrypt`] / [`encrypt_into`] — `(plaintext, scheme, key)`
//! - [`decrypt`] / [`decrypt_into`] — `(scheme_output, scheme, key)`
//!
//! Keys come last; data first. The per-scheme primitives live under
//! [`schemes`] for callers that already know the scheme statically.
//!
//! # Security model
//!
//! Symmetric **authenticated** encryption over a 64-byte master key.
//!
//! - **Authenticity**: every scheme is authenticated via the AEAD tag.
//! - **Determinism**: deterministic variants leak plaintext equality.
//! - **Key derivation**: the SIV schemes use the master key directly;
//! the GCM-SIV schemes derive a 32-byte key with `HKDF-Expand` over
//! the master (HMAC-SHA-256, info `gcmsiv`, shared by both GCM-SIV
//! schemes; Extract is skipped, as the master is already a uniform
//! pseudorandom key).
//! - **Nonce handling**: probabilistic schemes draw a fresh nonce per
//! call from the OS RNG. AES-SIV and AES-GCM-SIV are nonce-misuse
//! resistant — even under accidental reuse they degrade only to the
//! equality-leak property of the deterministic variants.
//! - **Side channels**: obcrypt relies on the underlying `aes-siv`,
//! `aes-gcm-siv`, and `hkdf` crates for constant-time primitives where
//! applicable; no extra side-channel hardening is added here.
//! - **Key zeroization**: [`Key`] is `ZeroizeOnDrop`; derived GCM-SIV
//! subkeys are held in `Zeroizing` buffers.
//!
//! See [`SECURITY.md`](https://gitlab.com/oboron/obcrypt-rs/-/blob/master/obcrypt/SECURITY.md)
//! for the full threat model and reporting policy.
//!
//! # Cargo features
//!
//! - `default = ["dgcmsiv", "pgcmsiv", "dsiv", "psiv"]` — every
//! production scheme.
//! - Per-scheme: `dgcmsiv`, `pgcmsiv`, `dsiv`, `psiv`.
//! - `mock` — adds the testing-only `mock1` / `mock2` schemes (no
//! encryption; not parseable from a string).
//!
//! Schemes are individually gated so binary size scales with the schemes
//! you actually use.
// At least one scheme feature must be enabled; otherwise `Scheme` is an
// empty enum and the encrypt/decrypt dispatch cannot be called. Surface a
// clear message instead of a cryptic downstream non-exhaustive-match error.
compile_error!;
pub use Error;
pub use Key;
pub use generate_key;
pub use Scheme;
// ---------------------------------------------------------------------------
// Encrypt
// ---------------------------------------------------------------------------
/// Encrypt `plaintext` under `scheme`, returning the scheme output bytes.
///
/// The output is the scheme's AEAD output directly (no marker); see the
/// [crate docs](crate#output-format) for the per-scheme layouts.
///
/// # Errors
///
/// - [`Error::EmptyPlaintext`] if `plaintext` is empty.
/// - [`Error::EncryptionFailed`] if the underlying AEAD primitive
/// reports failure — in practice only if `plaintext` exceeds the
/// AEAD's maximum message length (effectively unreachable for normal
/// in-memory use). RNG failure on the probabilistic schemes panics
/// rather than returning this error.
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "dsiv")] {
/// use obcrypt::{encrypt, Key, Scheme};
///
/// let key = Key::random();
/// let ct = encrypt(b"hello", Scheme::Dsiv, &key)?;
/// assert!(ct.len() > b"hello".len()); // includes the AEAD tag
/// # }
/// # Ok::<(), obcrypt::Error>(())
/// ```
/// Encrypt `plaintext` under `scheme`, appending the output to `out`.
///
/// The zero-extra-allocation form: the scheme writes its ciphertext
/// directly into `out`. On success `out` is extended by the scheme
/// output; on error `out` is left exactly as it was on entry
/// (all-or-nothing — never partially written).
///
/// # Errors
///
/// Same as [`encrypt`].
// ---------------------------------------------------------------------------
// Decrypt
// ---------------------------------------------------------------------------
/// Decrypt `scheme_output` under `scheme`, returning the plaintext.
///
/// The scheme is supplied by the caller — the output carries no marker
/// to detect it. Supplying the wrong scheme fails the authentication
/// check rather than returning garbage.
///
/// # Errors
///
/// - [`Error::PayloadTooShort`] if `scheme_output` is shorter than the
/// scheme's minimum layout length.
/// - [`Error::EmptyPayload`] (mock schemes only) if `scheme_output` is empty.
/// - [`Error::DecryptionFailed`] if the AEAD tag check fails (wrong key,
/// wrong scheme, or tampered output).
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "dsiv")] {
/// use obcrypt::{encrypt, decrypt, Key, Scheme};
///
/// let key = Key::random();
/// let ct = encrypt(b"hello", Scheme::Dsiv, &key)?;
/// let pt = decrypt(&ct, Scheme::Dsiv, &key)?;
/// assert_eq!(pt, b"hello");
/// # }
/// # Ok::<(), obcrypt::Error>(())
/// ```
/// Decrypt `scheme_output` under `scheme`, appending the plaintext to `out`.
///
/// On success `out` is extended by the recovered plaintext; on error
/// `out` is left exactly as it was on entry (all-or-nothing) — a failed
/// authentication never leaves partial or unverified bytes in `out`.
/// See [`decrypt`] for behavior.
///
/// # Errors
///
/// Same as [`decrypt`].