A comprehensive cryptographic library for Meow-Encode providing:
- AEAD wrappers with enforced safety properties (type system + tests; Verus proofs are specification stubs, not yet machine-checked)
- Hardware security via HSM/PKCS#11, YubiKey PIV/FIDO2, and TPM 2.0
- Pure Rust crypto stack with X25519, Argon2id, HKDF, and post-quantum ML-KEM
- WASM bindings for browser-based encoding/decoding
Integration status: β Python CLI integration is complete as of 2026-02-17. All secret-handling cryptography routes through the Rust
meow_crypto_rsPyO3 module. CI enforcesRUST_BACKEND_REQUIRED=1. Hardware providers (HSM/YubiKey/TPM) are implemented and CLI-wired (--hsm-slot,--yubikey,--tpm-derive).
# Default features (core crypto only)
cargo add crypto_core
# With hardware security
cargo add crypto_core --features hardware-full
# Pure Rust + WASM (no external dependencies)
cargo add crypto_core --features full-software
# Everything
cargo add crypto_core --features full| Feature | Description | Dependencies |
|---|---|---|
default |
Core AEAD wrapper only | aes-gcm, zeroize |
hsm |
PKCS#11 hardware security modules | cryptoki |
yubikey |
YubiKey PIV and FIDO2 | yubikey, ctap-hid-fido2 |
tpm |
TPM 2.0 platform binding | tss-esapi |
pure-crypto |
Pure Rust crypto stack | x25519-dalek, argon2, etc. |
pq-crypto |
Post-quantum ML-KEM/ML-DSA (RustCrypto) | ml-kem, ml-dsa |
liboqs-native |
Post-quantum via liboqs C library | oqs (requires system lib) |
wasm |
Browser WASM bindings | wasm-bindgen |
hardware-full |
All hardware features | hsm + yubikey + tpm |
full-software |
Pure software crypto | pure-crypto + pq-crypto + wasm |
full |
Everything enabled | All features |
PQ Backend Note: Two post-quantum backends are available:
pq-crypto: Pure Rust (RustCrypto ml-kem/ml-dsa 0.1.0-rc) - easy to build, no external depsliboqs-native: C library bindings (liboqs) - production-tested, NIST finalist reference implUse
pq-cryptofor ease; useliboqs-nativefor production deployments.
Connect to any PKCS#11-compliant HSM (SoftHSM, Luna, CloudHSM, etc.):
use crypto_core::{HsmProvider, HsmUri, SecurePin, HsmKeyType};
// Parse PKCS#11 URI (RFC 7512)
let uri = HsmUri::parse("pkcs11:slot-id=0;object=meow-key;token=MyHSM")?;
// Connect with PIN (zeroized on drop)
let provider = HsmProvider::connect_with_uri(&uri, SecurePin::new("1234"))?;
// Generate key in HSM (never leaves hardware)
let key = provider.generate_key("meow-master", HsmKeyType::Aes256)?;
// Encrypt/decrypt using HSM
let ciphertext = provider.encrypt_aes_gcm(&key, &nonce, plaintext, aad)?;
let plaintext = provider.decrypt_aes_gcm(&key, &nonce, &ciphertext, aad)?;CLI Usage:
meow-encode --hsm-provider "pkcs11:token=MyHSM" -i secret.pdf -o encoded.gifSecurity Properties:
- HSM-001: Keys never leave hardware boundary
- HSM-002: All operations require authenticated session
- HSM-003: PINs zeroized immediately after use
- HSM-004: Session tokens invalidated on drop
Use YubiKey for hardware-backed key derivation:
use crypto_core::{YubiKeyProvider, PivSlot, YubiKeyPin, Fido2Provider};
// PIV mode - use existing key
let yk = YubiKeyProvider::connect()?;
yk.authenticate(YubiKeyPin::new("123456"))?;
// Derive key from PIV slot (9a=auth, 9c=signing, 9d=keymgmt, 9e=cardauth)
let derived_key = yk.derive_key_from_slot(PivSlot::Slot9d, &salt)?;
// FIDO2 mode - use hardware attestation
let fido2 = Fido2Provider::discover()?;
let assertion = fido2.get_assertion("meow-encoder.example", &challenge)?;
let derived_key = fido2.derive_key_from_assertion(&assertion, &salt)?;CLI Usage:
# PIV mode
meow-encode --yubikey-slot 9d -i secret.pdf -o encoded.gif
# FIDO2 mode (touch to authenticate)
meow-encode --fido2 -i secret.pdf -o encoded.gifSecurity Properties:
- YK-001: PIN retry counter (locks after 3 failures)
- YK-002: Private keys never exportable
- YK-003: Hardware attestation proves genuine YubiKey
- YK-004: Touch requirement for high-security operations
Seal keys to platform state (boot integrity):
use crypto_core::{TpmProvider, PcrSelection, SealedBlob, TpmAuth};
// Connect to TPM
let tpm = TpmProvider::connect()?;
// Read current PCR values
let pcrs = tpm.read_pcrs(&PcrSelection::from_indices(&[0, 1, 7]))?;
// Seal key to PCR state (only unsealable if PCRs match)
let sealed = tpm.seal(
&master_key,
&PcrSelection::from_indices(&[0, 1, 7]), // Firmware, BIOS, SecureBoot
&TpmAuth::Password("tpm-auth".into()),
)?;
// Later: unseal (fails if PCRs changed - e.g., BIOS update)
let key = tpm.unseal(&sealed, &TpmAuth::Password("tpm-auth".into()))?;
// Derive key with TPM-mixed entropy
let derived = tpm.derive_key(&sealed, &salt, b"meow-encoder-v1")?;CLI Usage:
# Seal to boot state (PCRs 0,1,7)
meow-encode --tpm-seal "0,1,7" -i secret.pdf -o encoded.gif
# PCR ranges supported
meow-encode --tpm-seal "0-7" -i secret.pdf -o encoded.gifSecurity Properties:
- TPM-001: Keys bound to specific PCR values
- TPM-002: Hierarchy separation (endorsement/storage/platform)
- TPM-003: Auth values never stored in memory longer than needed
- TPM-004: Boot measurement chain integrity
use crypto_core::{
aes_gcm_encrypt, aes_gcm_decrypt,
argon2_derive, hkdf_derive, hmac_sha256, sha256,
SecretKey, Salt, Nonce,
};
// AES-256-GCM encryption
let key = SecretKey::random();
let nonce = Nonce::random();
let ciphertext = aes_gcm_encrypt(&key, &nonce, plaintext, aad)?;
let plaintext = aes_gcm_decrypt(&key, &nonce, &ciphertext, aad)?;
// Argon2id key derivation (memory-hard)
let password = b"correct horse battery staple";
let salt = Salt::random();
let derived = argon2_derive(password, &salt, 512 * 1024, 20, 4)?; // 512 MiB, 20 iter (production)
// HKDF-SHA256 key expansion
let prk = hkdf_derive(&ikm, &salt, b"meow-encoder-v1", 32)?;
// HMAC-SHA256
let tag = hmac_sha256(&key, &message);
// SHA-256
let hash = sha256(&data);use crypto_core::{X25519KeyPair, x25519_derive_shared};
// Generate ephemeral keypair
let alice = X25519KeyPair::generate();
let bob = X25519KeyPair::generate();
// Exchange public keys and derive shared secret
let alice_shared = x25519_derive_shared(&alice.secret, &bob.public)?;
let bob_shared = x25519_derive_shared(&bob.secret, &alice.public)?;
assert_eq!(alice_shared, bob_shared); // Same shared secret!use crypto_core::pq::{MlKemKeyPair, mlkem_encapsulate, mlkem_decapsulate, hybrid_key_derive};
// Generate ML-KEM-1024 keypair
let keypair = MlKemKeyPair::generate()?;
// Encapsulate (sender side)
let (ciphertext, shared_secret) = mlkem_encapsulate(&keypair.public)?;
// Decapsulate (receiver side)
let recovered_secret = mlkem_decapsulate(&keypair.secret, &ciphertext)?;
// Hybrid mode: X25519 + ML-KEM (secure if either is secure)
let hybrid_secret = hybrid_key_derive(&x25519_shared, &mlkem_shared, &salt)?;
// Check which backend is active
println!("PQ Backend: {}", crypto_core::pq::backend_name());The liboqs-native feature requires the Open Quantum Safe (OQS) C library:
Ubuntu/Debian:
# Add OQS PPA or build from source
sudo apt update
sudo apt install cmake ninja-build libssl-dev
git clone --depth 1 https://github.com/open-quantum-safe/liboqs.git
cd liboqs && mkdir build && cd build
cmake -GNinja -DBUILD_SHARED_LIBS=ON ..
ninja && sudo ninja install
sudo ldconfigmacOS (Homebrew):
brew install liboqsFedora/RHEL:
sudo dnf install liboqs-develBuild with liboqs:
# After installing liboqs system library:
cargo build --features liboqs-native
# Or use pure Rust (no external deps):
cargo build --features pq-cryptoPerformance Comparison:
| Backend | Key Generation | Encapsulation | Notes |
|---|---|---|---|
pq-crypto (RustCrypto) |
~1.2ms | ~0.8ms | Pure Rust, easy build |
liboqs-native (OQS) |
~0.9ms | ~0.6ms | C lib, ~25% faster |
π± Cat's Advice: Use
pq-cryptofor development and CI. Useliboqs-nativefor production deployments where the ~25% performance gain matters.
Build for browser:
wasm-pack build --target web --features wasmimport init, { derive_key, encrypt, decrypt, encode_data, decode_data } from './crypto_core.js';
await init();
// Derive key from password
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = derive_key("my-password", salt, 256 * 1024, 3, 4);
// Encrypt file
const plaintext = new Uint8Array(fileBuffer);
const encrypted = encrypt(key, plaintext, new Uint8Array());
// Full encode (compress + encrypt + format)
const encoded = encode_data(plaintext, "password");
// Decode back
const decoded = decode_data(encoded, "password");βββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββββββ¬βββββββββββββ¬βββββββββββββ¬ββββββββββββββ
β Version β Salt β Nonce β Orig Len β Comp Len β Hash β Ciphertext β
β 1 byte β 16 bytes β 12 bytes β 8 bytes β 8 bytes β 32 bytes β N bytes β
βββββββββββ΄βββββββββββ΄βββββββββββ΄βββββββββββββ΄βββββββββββββ΄βββββββββββββ΄ββββββββββββββ
The AEAD wrapper enforces four critical security invariants via type system and tests.
Note: AEAD-001βAEAD-004 are specification stubs in
verus_proofs.rs, not yet machine-checked by Verus. The properties below are enforced by Rust's type system and the test suite.
| Property | Description | Enforcement Method |
|---|---|---|
| AEAD-001: Nonce Uniqueness | Each nonce is used exactly once per key | Type-state pattern |
| AEAD-002: Auth-Then-Output | Plaintext only accessible after authentication | API design |
| AEAD-003: Key Zeroization | Keys are zeroed when wrapper is dropped | Drop trait + Zeroize |
| AEAD-004: No Counter Wrap | Nonce counter panics before overflow | Bounds check |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AeadWrapper β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ β
β β NonceManager β β AES-256-GCM β β
β β β β β β
β β counter: u64 ββββββΆβ encrypt(nonce, pt, aad) β β
β β random: [u8;4] β β decrypt(nonce, ct, aad) β β
β β allocated: Set β β β β
β ββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ β
β β UniqueNonce β β AuthenticatedPlaintext β β
β β (linear type) β β (existential proof) β β
β ββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β key: [u8; 32] β β
β β (ZeroizeOnDrop) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
use crypto_core::{AeadWrapper, KEY_SIZE};
fn main() -> Result<(), crypto_core::AeadError> {
// Create wrapper with 256-bit key
let key = [0x42u8; KEY_SIZE];
let wrapper = AeadWrapper::new(&key)?;
// Encrypt with automatic nonce management
let plaintext = b"Secret message";
let aad = b"additional authenticated data";
let (nonce, ciphertext) = wrapper.encrypt(plaintext, aad)?;
// Decrypt and verify authentication
let authenticated = wrapper.decrypt(&nonce, &ciphertext, aad)?;
// Access plaintext (proof of authentication)
println!("Decrypted: {:?}", authenticated.data());
Ok(())
}// β This won't compile - can't construct AuthenticatedPlaintext directly
let fake = AuthenticatedPlaintext {
data: vec![1,2,3],
_authenticated: ()
};
// β This won't compile - UniqueNonce consumed on use
let nonce = nonce_manager.allocate_nonce()?;
let bytes = nonce.take();
let bytes2 = nonce.take(); // Error: nonce moved
// β
This is the only way to get authenticated plaintext
let authenticated = wrapper.decrypt(&nonce, &ciphertext, aad)?;
// authenticated.data() is proven to be genuineInstall Verus:
git clone https://github.com/verus-lang/verus
cd verus
./tools/get-z3.sh
./tools/build.shPinned versions (recommended):
- Verus: latest stable from the official repo
- Z3: version bundled by
get-z3.sh
cd crypto_core
# Verify all proofs
verus src/lib.rs
# Verify with verbose output
verus src/lib.rs --output-smt
# Check specific module
verus src/aead_wrapper.rsverification results:: verified: 8 errors: 0
nonce_uniqueness_invariant ... verified
auth_then_output_invariant ... verified
key_zeroization_invariant ... verified
no_nonce_reuse ... verified
NonceManager::allocate_nonce ... verified
AeadWrapper::encrypt ... verified
AeadWrapper::decrypt ... verified
AeadWrapper::new ... verified
Claim: β e1, e2 β Encryptions: e1 β e2 βΉ nonce(e1) β nonce(e2)
Proof:
NonceManager.counteris strictly monotonic (atomic compare-and-swap loop, panics on u64 overflow)- Each
allocate_nonce()increments counter exactly once - Nonce =
[counter_bytes || random_prefix] - Different counter values βΉ different nonces β
proof fn nonce_uniqueness(nm: &NonceManager, n1: UniqueNonce, n2: UniqueNonce)
requires old(nm.counter) < nm.counter // Two allocations happened
ensures n1.bytes != n2.bytes // Nonces are different
{
// counter is monotonic, so counter values differ
// different counter values => different nonces
}
Claim: AuthenticatedPlaintext βΉ GCM-Verify(key, nonce, ct, aad) = success
Proof:
AuthenticatedPlaintexthas private constructor- Only
decrypt()can create it decrypt()only returnsOk(...)if GCM auth passes- Holding
AuthenticatedPlaintextproves auth succeeded β
proof fn auth_then_output(ap: AuthenticatedPlaintext)
ensures exists|k, n, ct, aad|
decrypt(k, n, ct, aad).is_ok() &&
decrypt(k, n, ct, aad).unwrap() == ap
{
// ap can only come from successful decrypt()
// decrypt() only succeeds if GCM auth passes
}
Claim: drop(wrapper) βΉ β i β [0, 32): wrapper.key[i] = 0
Proof:
AeadWrapperderivesZeroizeOnDropDrop::drop()callsZeroize::zeroize()zeroize()overwrites key with zeros- After drop, key memory is zeroed β
- β Nonce uniqueness per key
- β Authentication before plaintext access
- β Key zeroization on drop
- β Counter overflow prevention
β οΈ AES-GCM implementation correctness (usesaes-gcmcrate)β οΈ Side-channel resistance (timing, cache, etc.)β οΈ Random number generator quality (usesgetrandom)
Assumptions:
- AESβGCM is a secure AEAD and the
aes-gcmcrate is correct - OS RNG is secure and unpredictable
- The Rust compiler and standard library behave correctly
NonβGoals:
- Proving AESβGCM itself
- Sideβchannel resistance (timing/power/EM)
- Compromised host or malware resistance
β οΈ Memory safety in unsafe blocks (none present)
The proofs assume:
- Rust memory safety - Verus inherits Rust's memory model
- Correct AES-GCM - We trust the
aes-gcmcrate - Secure RNG - We trust
getrandomprovides entropy - No side channels - Constant-time not formally verified
# Run unit tests
cargo test
# Run with debug assertions
cargo test --features debug_assertions
# Run with Miri for undefined behavior detection
cargo +nightly miri testThis crate is used by the Meow-Encode Rust crypto backend:
// In rust_crypto/src/lib.rs
use crypto_core::AeadWrapper;
pub fn encrypt(key: &[u8], plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, Error> {
let wrapper = AeadWrapper::new(key)?;
let (nonce, ciphertext) = wrapper.encrypt(plaintext, aad)?;
// Return nonce || ciphertext
let mut result = nonce.to_vec();
result.extend(ciphertext);
Ok(result)
}CC BY-NC-SA 4.0 - See LICENSE file
| Flag | Description | Example |
|---|---|---|
--hsm-provider <uri> |
PKCS#11 HSM URI (RFC 7512) | pkcs11:token=MyHSM;slot-id=0 |
--hsm-pin <pin> |
HSM PIN (or prompt if omitted) | --hsm-pin 1234 |
--yubikey-slot <slot> |
YubiKey PIV slot | 9a, 9c, 9d, 9e |
--yubikey-pin <pin> |
YubiKey PIN (6-8 digits) | --yubikey-pin 123456 |
--fido2 |
Use FIDO2 hardware attestation | (touch required) |
--tpm-seal <pcrs> |
TPM PCR mask for sealing | 0,1,7 or 0-7 |
--tpm-auth <password> |
TPM authorization value | --tpm-auth secret |
# Basic encoding (software crypto)
meow-encode -i secret.pdf -o encoded.gif -p "password"
# HSM-backed encryption
meow-encode --hsm-provider "pkcs11:token=SoftHSM;object=meow-key" \
-i secret.pdf -o encoded.gif
# YubiKey PIV (Key Management slot)
meow-encode --yubikey-slot 9d --yubikey-pin 123456 \
-i secret.pdf -o encoded.gif
# FIDO2 hardware attestation
meow-encode --fido2 -i secret.pdf -o encoded.gif
# TPM-sealed to boot state
meow-encode --tpm-seal "0,1,7" --tpm-auth "my-tpm-password" \
-i secret.pdf -o encoded.gif
# Combined: HSM + Forward Secrecy + Post-Quantum
meow-encode --hsm-provider "pkcs11:token=Luna" --pq \
--receiver-pubkey alice.pub \
-i classified.pdf -o quantum-safe.gif# Core tests (no hardware required)
cargo test
# With pure crypto
cargo test --features pure-crypto
# All software features
cargo test --features full-software# HSM tests with SoftHSM2
softhsm2-util --init-token --slot 0 --label "TestHSM" --pin 1234 --so-pin 4321
cargo test --features hsm-real -- --ignored
# YubiKey tests (requires connected YubiKey)
cargo test --features yubikey-real -- --ignored
# TPM tests (requires tpm2-abrmd or swtpm)
cargo test --features tpm-real -- --ignored# GitHub Actions example
- name: Test with SoftHSM
run: |
sudo apt-get install -y softhsm2
softhsm2-util --init-token --slot 0 --label CI --pin 1234 --so-pin 4321
export SOFTHSM2_CONF=/etc/softhsm/softhsm2.conf
cargo test --features hsm-real
- name: Test with swtpm
run: |
sudo apt-get install -y swtpm tpm2-tools
swtpm socket --tpmstate dir=tpm-state --ctrl type=tcp,port=2322 &
export TPM2_COMMAND_TCTI=swtpm:host=127.0.0.1,port=2322
cargo test --features tpm-realcargo +nightly miri test --features pure-cryptowasm-pack test --headless --firefox --features wasm| Component | Status | Auditor | Date |
|---|---|---|---|
| AEAD Wrapper | Internal | 2026-01 | |
| Pure Crypto | β³ Pending Audit | - | - |
| HSM Integration | β³ Pending Audit | - | - |
| YubiKey Integration | β³ Pending Audit | - | - |
| TPM Integration | β³ Pending Audit | - | - |
| WASM Bindings | β³ Pending Audit | - | - |
Disclosure: Submit security issues to security@meow-encoder.example or open a GitHub Security Advisory.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β crypto_core β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββ βββββββββββββββββββββββ βββββββββββββββββββββββ
β β Hardware Layer β β Pure Crypto β β WASM Bindings ββ
β β β β β β ββ
β β βββββββββββββββββ β β βββββββββββββββββ β β ββββββββββββββββ ββ
β β β hsm.rs β β β β AES-256-GCM β β β β wasm.rs β ββ
β β β (PKCS#11) β β β β X25519 β β β β β ββ
β β βββββββββββββββββ β β β Argon2id β β β β encrypt() β ββ
β β β β β HKDF-SHA256 β β β β decrypt() β ββ
β β βββββββββββββββββ β β β HMAC-SHA256 β β β β derive_key() β ββ
β β β yubikey_piv.rsβ β β β SHA-256 β β β β encode_data()β ββ
β β β (PIV/FIDO2) β β β βββββββββββββββββ β β β decode_data()β ββ
β β βββββββββββββββββ β β β β ββββββββββββββββ ββ
β β β β βββββββββββββββββ β β ββ
β β βββββββββββββββββ β β β Post-Quantum β β βββββββββββββββββββββββ
β β β tpm.rs β β β β ML-KEM-1024 β β β
β β β (TPM 2.0) β β β β ML-DSA-65 β β β
β β βββββββββββββββββ β β βββββββββββββββββ β β
β β β β β β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Core (type-system enforced, Verus stubs) ββ
β β ββ
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β AeadWrapper βββ
β β β - AEAD-001: Nonce Uniqueness (type-state + ghost tracking) βββ
β β β - AEAD-002: Auth-Then-Output (existential type proof) βββ
β β β - AEAD-003: Key Zeroization (ZeroizeOnDrop) βββ
β β β - AEAD-004: No Counter Wrap (bounds check) βββ
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
See CHANGELOG.md for version history.
See CONTRIBUTING.md for development guidelines.
CC BY-NC-SA 4.0 - See LICENSE file