encryption is a no_std Ouroboros authentication engine with Speck-128/256, Davies-Meyer key stretching, and CTR-mode payload execution.
#![no_std]crate API suitable for microcontroller targets- Constant-time MAC verification via
subtle - Entropy abstraction limited to jitter sampling during key stretching
- Included host demo for local verification and debugging
Run this from a clean checkout to verify the crate works end-to-end:
cargo test --all-features && cargo run --features demo --bin demoAt the prompt, enter:
orbit olive ladder marble quartz canyon ripple saddle violet ember walnut falcon
Expected result:
hello
Add to your Cargo.toml:
[dependencies]
encryption = { git = "https://github.com/mytechnotalent/encryption" }| Area | macOS | Windows | Linux |
|---|---|---|---|
Rust crate build (cargo test --all-features) |
Tested | Toolchain-supported; verify in your environment | Toolchain-supported; verify in your environment |
Demo binary (cargo run --features demo --bin demo) |
Tested | Supported (crossterm) |
Supported (crossterm) |
scripts/dec.py (argon2-cffi, pynacl) |
Tested | Supported with Python 3 + native deps | Supported with Python 3 + native deps |
Toolchain used for latest validation:
- Rust stable (
cargo) - Python 3.12+ with
argon2-cffiandpynacl
Implement EntropySource for your platform, then decrypt a passphrase and consume the decoded payload operations:
# use encryption::{EntropySource, OuroborosEngine, PayloadOp};
# struct MockEntropy;
# impl EntropySource for MockEntropy {
# fn get_jitter(&self) -> u8 { 0 }
# }
let mut engine = OuroborosEngine::new(MockEntropy);
let _payload = engine.decrypt_with_ciphertext(b"hello", &encryption::CIPHERTEXT).unwrap();
for op in engine.payload_ops() {
match op {
PayloadOp::LedState(on) => {
let _ = on;
}
PayloadOp::TxBytes(bytes) => {
let _ = bytes;
}
}
}The library no longer owns UART, LED, or delay behavior. Callers are responsible for mapping PayloadOp values onto their own platform I/O.
Payload decoding uses a fixed dispatch shape: one LED state from byte 0 and transmitted bytes from 1..8.
Authentication failure is reported as EngineError::AuthenticationFailed rather than a successful decrypt of a zeroed buffer.
Use this project when you want:
- A small, auditable
no_stdRust authentication/decryption engine for embedded-oriented flows. - Explicit payload operation decoding (
LedState,TxBytes) without hardwiring platform I/O. - A paired host artifact generator (
scripts/dec.py) and firmware-friendly payload contract.
Prefer mainstream higher-level crypto libraries when you need:
- A general-purpose application crypto toolkit with broad algorithm agility.
- Drop-in network/application protocol primitives rather than fixed demo payload dispatch.
- Third-party audited compliance profiles for production certification targets.
A host-side demo with raw terminal input is included. It is intentionally kept out of the default library build so the crate stays clean for embedded consumers.
cargo run --features demo --bin demoThe demo enforces a strict passphrase policy: Enter exactly 12 lowercase words separated by ASCII whitespace (spaces/tabs/newlines are normalized by parsing). Inputs like hello, empty lines, or non-policy strings are rejected with a policy hint.
Successful decrypt also requires that your input exactly matches the passphrase used to generate scripts/demo_artifact.json.
If you generated the artifact with the default README command, enter this exact 12-word phrase:
orbit olive ladder marble quartz canyon ripple saddle violet ember walnut falcon
On success, the demo prints hello (plus CRLF by default).
If your artifact was generated with a different passphrase, this phrase will return Authentication failed.
Expected behavior summary:
correct phrase for current artifact -> hello
wrong phrase for current artifact -> Authentication failed.
The demo uses crossterm, so it should work on the major macOS, Linux, and Windows terminals supported by that crate. It is not a guarantee for every pseudo-terminal, IDE-integrated terminal, or unusual serial console environment.
For terminal-entered secrets that need higher modeled offline guessing cost, enable the hardened feature:
cargo test --features hardenedHardened APIs are available on OuroborosEngine:
encrypt_hardened(passphrase, kdf_params, salt, nonce, payload)decrypt_hardened(passphrase, kdf_params, salt, nonce, ciphertext_and_tag)
Use per-ciphertext random values:
HARDENED_SALT_SIZE = 16HARDENED_NONCE_SIZE = 24
For host/desktop systems where you want a higher per-guess verification cost, the recommended KDF policy for terminal-entered secrets is Argon2id with calibrated runtime near 10 seconds on your baseline hardware. A practical starting point is:
memory_kib: 1_048_576(1 GiB)iterations: 3parallelism: 1
Important: passphrase expansion to 32 bytes does not create entropy. Security still depends on the entropy of what the human enters.
The scripts/dec.py script is hardened-only and writes a JSON artifact consumed by the demo at runtime.
Run it with a strict 12-word lowercase passphrase:
python3 scripts/dec.py --key "orbit olive ladder marble quartz canyon ripple saddle violet ember walnut falcon" --text "hello"By default this updates scripts/demo_artifact.json directly (no Rust copy/paste needed).
dec.py enforces the same passphrase policy as the engine and demo: exactly 12 lowercase words under ASCII-whitespace normalization.
If your local .venv has native-extension architecture mismatch errors (for example, _cffi_backend incompatible architecture), rebuild it first:
rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -U pip setuptools wheel
python3 -m pip install argon2-cffi pynaclOtherwise, install required Python packages first:
python3 -m pip install argon2-cffi pynaclYou can override the output path if needed:
python3 scripts/dec.py --key "<12 lowercase words>" --text "hello" --out scripts/demo_artifact.jsonThen rebuild and run:
cargo test --all-features
cargo run --features demo --bin demoPayload layout is fixed:
- byte
0: LED state (1on,0off) - bytes
1..7: UART output bytes
By default, dec.py appends CRLF (\r\n) to --text, so output must fit within 7 bytes after that append. Use --no-crlf to keep full 7-byte capacity for raw text.
You can also keep your own ciphertext outside the crate and pass it directly to decrypt_with_ciphertext().
src/lib.rs: crate root and public re-exportssrc/entropy.rs: jitter sampling abstractionsrc/engine.rs: passphrase decryption and payload decodingsrc/crypto/speck.rs: Speck-128/256 block cipher primitivessrc/crypto/hash.rs: Davies-Meyer stretchingsrc/crypto/ctr.rs: CTR decryption and MAC masking
The Ouroboros engine uses:
- Speck-128/256 block cipher with 34 rounds
- Davies-Meyer key stretching with 24,576 iterations
- CTR mode decryption using a nonce derived from the stretched hash
- Constant-time MAC verification over the trailing payload bytes
Speck-128/256 round function:
x' = (ROR64(x, 8) + y) xor k_i
y' = (ROL64(y, 3) xor x')
Key schedule for (k_0, l_0, l_1, l_2):
l_{i mod 3} = (ROR64(l_{i mod 3}, 8) + k_i) xor i
k_{i+1} = (ROL64(k_i, 3) xor l_{i mod 3})
Davies-Meyer stretching (24,576 iterations):
H_0 = IV
H_j = E_K(H_{j-1}), j = 1..24576
hash = H_24576 xor IV
CTR decryption for 48-byte entries (three blocks):
CTR_b = hash[0:8] || b || 0^7
KS_b = E_K(CTR_b)
P_b = C_b xor KS_b
Branchless MAC verification over bytes 16..47 against 0xAA:
diff = OR_i (P[i] xor 0xAA)
valid = (diff == 0)
Short version: there is no known cryptographic shortcut that bypasses the passphrase and directly reveals the message. Under the standard assumption that full-round Speck-128/256 resists key recovery, recovering the plaintext without the passphrase still reduces to a practical key-recovery problem.
- Wrong key gives wrong keystream:
P = C xor KS(K)only yields the correct plaintext with the correctK. - MAC gating is not the core protection: even if a fault bypassed MAC handling, a wrong key still decrypts to wrong bytes.
- The stretching loop is sequential:
H_jdepends onH_{j-1}for all 24,576 iterations.
Known-keystream observation:
KS[16:48] = C[16:48] xor 0xAA
This leakage is real (because the MAC plaintext is fixed), but converting those known keystream blocks into the 256-bit key still requires a practical key-recovery attack on full-round Speck-128/256. No such practical attack is known for the full configured variant used here.
This is not a formal proof. It is an implementation claim under current public cryptanalysis assumptions.
Scope boundary: claims here are limited to intended execution with unmodified firmware; firmware patching, instruction-level control, and active fault injection are out of scope.
Short answer: not in the strict post-quantum-cryptography (PQC) sense.
- This crate does not implement a NIST PQC KEM or signature scheme.
- Security here is symmetric-key + password-guessing cost.
- Against Grover-style brute force, symmetric search exponents are roughly halved.
So the right claim is: high modeled brute-force cost under stated entropy and KDF assumptions, not "quantum-proof."
Assume offline guessing, where each guess runs the full verifier.
- Let
Hbe effective passphrase entropy (bits). - Let
rbe classical guesses/second. - Let
r_qbe quantum oracle evaluations/second.
Average classical crack time:
T_avg_classical_years = 2^(H-1) / (r * 31,557,600)
Optimistic Grover-style estimate (very favorable to attacker):
T_avg_quantum_years = 2^(H/2 - 1) / (r_q * 31,557,600)
The quantum formula above is only a coarse upper-bound model. It assumes a scalable fault-tolerant quantum machine can evaluate the full password oracle repeatedly, which is far from current reality.
All values below are average time to crack.
| Scenario | Assumptions | Classical | Quantum (optimistic Grover model) |
|---|---|---|---|
| Weak human secret | H = 40, r = 10^9/s |
1.74e-5 years (~9.2 minutes) |
1.66e-11 years (~0.52 ms) |
| Better but still human | H = 60, r = 10^9/s |
18.27 years |
1.70e-8 years (~0.54 s) |
| Strong random secret | H = 80, r = 10^9/s |
1.92e7 years |
1.74e-5 years (~9.2 minutes) |
| Very strong random secret | H = 100, r = 10^9/s |
2.01e13 years |
0.0178 years (~6.5 days) |
| Hardened policy target (12 Diceware words) | H ≈ 155.1, r = 0.1/s (10 s/guess Argon2id calibration) |
~7.76e39 years |
~3.51e16 years |
Age-of-universe comparison for the hardened policy row (age of universe ≈ 1.38e10 years):
Classical ratio = 7.76e39 / 1.38e10 ≈ 5.6e29
Quantum ratio = 3.51e16 / 1.38e10 ≈ 2.5e6
That is approximately:
- Classical: about
5.6e29times the age of the universe (~560 octillionx). - Quantum (optimistic Grover model): about
2.5e6times the age of the universe (~2.5 millionx).
- The dominant variable is entropy. Low-entropy human secrets are breakable regardless of algorithm branding.
- Hardened mode only helps if operators enforce high-entropy secrets and keep Argon2id calibration expensive.
- Non-memory-hard constructions should not be positioned as high-security password verifiers.
SECONDS_PER_YEAR = 365.25 * 24 * 3600
def avg_years_classical(H, r):
return 2 ** (H - 1) / (r * SECONDS_PER_YEAR)
def avg_years_quantum(H, r_q):
return 2 ** (H / 2 - 1) / (r_q * SECONDS_PER_YEAR)
print(avg_years_classical(155.1, 0.1))
print(avg_years_quantum(155.1, 0.1))Behavioral parity is continuously tested in Rust for:
- passphrase length handling (
1..=32valid,0and>32rejected) - ciphertext-entry size (
48bytes) - authentication failure behavior (
AuthenticationFailed) - payload dispatch shape: LED state from byte
0, UART bytes from1..8 - default bundled ciphertext vs caller-supplied ciphertext equivalence
This crate is an implementation project, not a third-party audited cryptography library. Review the design, assumptions, and target-specific integration before using it in production.
This crate is suitable as a microcontroller integration component because the library itself is #![no_std] and does not require the host demo stack. Target-specific correctness still depends on the caller providing an appropriate EntropySource and mapping decoded PayloadOp values onto local I/O.
MIT — see LICENSE.