Pure-Rust ChaCha20-Poly1305 AEAD (Authenticated Encryption with Associated Data) implementation for no_std and bare-metal targets.
The standard chacha20poly1305 crate (from RustCrypto) fails to compile on certain bare-metal targets like x86_64-unknown-none with LLVM errors:
LLVM ERROR: Do not know how to split the result of this operator!
This happens because the crate depends on other crates that use SIMD intrinsics unavailable on all targets. This pure-Rust implementation avoids SIMD entirely, making it portable to any Rust target.
- 100% Pure Rust: No assembly, no SIMD, no platform-specific code
no_stdcompatible: Works in bare-metal and embedded environments- Constant-time: Side-channel resistant implementation
- RFC 8439 compliant: Passes official test vectors
- AEAD: Provides both confidentiality and authenticity
- Simple API: Encrypt and decrypt with associated data support
- 256-bit key, 96-bit nonce
- 128-bit authentication tag
- Constant-time operations
- ~4 cycles/byte on modern x86_64
- Resists tampering, replay, and forgery attacks
Add to your Cargo.toml:
[dependencies]
chacha20poly1305-nostd = "0.1"use chacha20poly1305_nostd::ChaCha20Poly1305;
let key = [0u8; 32]; // 256-bit key
let nonce = [0u8; 12]; // 96-bit nonce (MUST be unique per message)
let cipher = ChaCha20Poly1305::new(&key).unwrap();
// Encrypt
let plaintext = b"Hello, Luna!";
let ciphertext = cipher.encrypt(&nonce, plaintext, None).unwrap();
// ciphertext = encrypted_data || 16-byte authentication tag
// Decrypt
let decrypted = cipher.decrypt(&nonce, &ciphertext, None).unwrap();
assert_eq!(decrypted, plaintext);use chacha20poly1305_nostd::ChaCha20Poly1305;
let key = [0u8; 32];
let nonce = [0u8; 12];
let cipher = ChaCha20Poly1305::new(&key).unwrap();
let plaintext = b"Secret message";
let aad = b"public header"; // Authenticated but not encrypted
// Encrypt with AAD
let ciphertext = cipher.encrypt(&nonce, plaintext, Some(aad)).unwrap();
// Decrypt with AAD (must match!)
let decrypted = cipher.decrypt(&nonce, &ciphertext, Some(aad)).unwrap();
assert_eq!(decrypted, plaintext);
// Wrong AAD fails authentication
let result = cipher.decrypt(&nonce, &ciphertext, Some(b"wrong"));
assert!(result.is_err());ChaCha20-Poly1305 combines two primitives:
-
ChaCha20: Stream cipher for encryption
- 20 rounds of the ChaCha quarter-round function
- 256-bit key, 96-bit nonce, 32-bit counter
-
Poly1305: Message authentication code
- One-time MAC using key derived from ChaCha20
- 128-bit authentication tag
- Generate Poly1305 key from ChaCha20 block 0
- Encrypt plaintext with ChaCha20 (starting from block 1)
- Compute Poly1305 MAC over:
AAD || pad || ciphertext || pad || lengths - Append authentication tag to ciphertext
- Uses
poly1305-nostdfor authentication - Uses standard
chacha20crate (no SIMD issues) - Constant-time tag comparison to prevent timing attacks
- No heap allocations except for output buffers
On modern x86_64 CPUs:
- ~4 cycles/byte for encryption + authentication
- ~2 µs for 1 KB message
(Performance varies by target; bare-metal may differ)
Tested on:
x86_64-unknown-none(bare-metal)x86_64-unknown-linux-gnu(std)- Other targets should work but are untested
- Never reuse nonces: Each (key, nonce) pair must be unique
- Nonce handling: Use a counter or random nonces (96 bits provides good margin)
- AAD validation: Ensure AAD matches on decryption
- Tag verification: Always check decrypt() return value
- WireGuard: Transport data encryption
- SSH:
chacha20-poly1305@openssh.comcipher - TLS 1.3: Modern cipher suite
- Secure messaging: Signal Protocol, Matrix
- File encryption: With proper key derivation
Run the test suite:
cargo testIncludes RFC 8439 Appendix A.5 test vector.
Licensed under Apache License 2.0.
- RFC 8439: ChaCha20-Poly1305 AEAD
- ChaCha20 and Poly1305 for IETF Protocols
- The Poly1305-AES message-authentication code
Contributions welcome! This crate was extracted from a bare-metal OS project to help the Rust embedded and bare-metal community overcome LLVM SIMD limitations.