Safe Rust bindings for the butane cryptographic engine
Zero-cost abstractions · Safe RAII semantics · Result-based Error Handling
btne-ffi-rs provides safe, idiomatic Rust wrappers around the butane C library. It maps the raw C FFI into a high-level Rust interface, ensuring safety through RAII (Resource Acquisition Is Initialization), proper error handling, and memory lifecycle management without introducing unnecessary overhead.
- RAII
Butanecontext: Automatically handlesbutane_freeandbutane_cleanonDrop, preventing memory leaks and ensuring sensitive key material is wiped. - Idiomatic Error Handling: Maps all
BUTANE_ERR_*C codes to a convenientButaneErrorenum using Rust'sResulttype. - Zero-copy FFI: Avoids unnecessary allocations during encryption/decryption until data needs to be safely returned to the user.
- Thread-safe: Implements
SendandSyncfor the context, allowing safe usage across concurrent threads. - Direct Raw Access: The raw FFI bindings are still available under the
ffimodule if needed.
You need libbutane.a (the static C library) available. The build.rs script automatically links against the library provided in the static/ directory. Platform-specific linking (like Security.framework on macOS or bcrypt on Windows) is also handled automatically.
cargo build
cargo testbtne-ffi-rs/
├── src/
│ ├── lib.rs # Crate root and public re-exports
│ ├── ffi.rs # Raw C bindings and constants
│ ├── error.rs # ButaneError enum and Result mapping
│ └── safe.rs # High-level RAII wrapper (Butane struct)
├── static/
│ └── libbutane.a # Pre-compiled static archive
├── build.rs # Build script for static linking
├── neomake.toml # neomake build
└── Cargo.toml # Package metadata
The safe wrapper encapsulates the butane context and provides an easy-to-use API:
use btne_ffi::{Butane, Argon2Params};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Initialize context and derive keys
let salt = [0x42u8; 16]; // ffi::BUTANE_SALT_SIZE
let params = Argon2Params::defaults()?;
let engine = Butane::new("my-secure-password", &salt, params)?;
// 2. Encrypt data
let plaintext = b"Hello, secure world!";
let encrypted = engine.encrypt(plaintext)?;
// The returned EncryptedData struct holds ciphertext, nonce, and tag together
// encrypted.ciphertext, encrypted.nonce, encrypted.tag
// 3. Decrypt data
let decrypted = engine.decrypt_encrypted(&encrypted)?;
assert_eq!(plaintext, decrypted.as_slice());
// 4. Memory is securely cleaned and context is freed automatically when `engine` goes out of scope!
Ok(())
}Licensed under the Apache License 2.0.
Copyright 2026; Mohammed Sajid Shaik