llave
March 21, 2026 · View on GitHub
Alternative implementation of Llave — Spain's digital identity system for interacting with public administration services. CLI and Flutter (mobile/desktop) clients sharing a single Rust core.
Features
- Device activation: Register your device with Llave
- PIN requests: Get a temporary Llave PIN for accessing government services
- NIF checking: Verify if a NIF/NIE is registered in Llave
- DNI/NIE auth: Weak authentication using ID card data
- QR authentication: Authenticate by scanning QR codes
- Account data: View your Llave account information
- Operations history: Browse past authentication operations
- Structured output: JSON by default,
--plainfor human-readable text - Secure storage: Credentials stored via system keyring (fallback to XDG data dir)
- Flutter apps: iOS, Android, and desktop powered by the same Rust core via FFI
Project Structure
crates/
├── llave-core/ # Shared Rust library: API client, auth flows, config, crypto, session
├── llave-core-ffi/ # FFI bridge layer for Flutter (flutter_rust_bridge)
└── llave-cli/ # CLI binary (`llave`)
flutter/ # Flutter app (iOS, Android, desktop)
Installation
With Nix (recommended)
nix run github:openhacienda/llave#llave-cli
Development shell
nix develop
cargo build --release
From source
cargo install --path crates/llave-cli
Usage
Activate your device
llave activate --nif 12345678Z
Request a Llave PIN
llave pin
Check NIF registration
llave check --nif 12345678Z
DNI/NIE weak authentication
llave dni-auth --nif 12345678Z --fecha 01-01-2030 --soporte ABC123456
View session status
llave status
View account data
llave my-data
View operations history
llave history
Llave Móvil (push notification replacement)
Since the CLI cannot receive Firebase push notifications, it polls the server instead.
Check for pending requests (single poll):
llave pending
Listen continuously for authentication requests:
llave listen # default: 5s interval, 60 attempts
llave listen --interval 3 --max-attempts 120 # custom polling
Confirm a pending authentication request:
llave confirm --token <TOKEN> --idp-code <IDP_CODE>
Reject a pending authentication request:
llave reject --token <TOKEN> --idp-code <IDP_CODE>
QR authentication
llave qr --value "qr-code-content"
Log out
llave logout
Output formats
By default, all commands output JSON:
{
"pin": "123456",
"time_to_live_seconds": "180",
"nif": "12345678Z"
}
Use --plain for human-readable output:
pin: 123456
time_to_live_seconds: 180
nif: 12345678Z
Use -v / --verbose for debug logging.
Configuration
Configuration and session data are stored following XDG conventions:
- Config:
$XDG_CONFIG_HOME/llave-cli/config.json - Session: Stored in system keyring, or
$XDG_DATA_HOME/llave-cli/session.jsonas fallback
Documentation
Technical documentation is available as an mdbook under book/. Based on reverse engineering of the official es.aeat.pin24h Android app (v6.2.5).
To build and serve locally:
nix run nixpkgs#mdbook -- serve book/
Topics covered:
- Architecture — system overview, AEAT infrastructure, Android app structure
- API Reference — endpoints, request/response formats, error codes, constants
- Authentication Flows — PIN request, Clave Movil, QR, DNI/NIE + SMS activation, SAML/DNIe
- Implementation Guide — cookie management, client identity, redirect handling, session persistence
- Cryptography — browser encryption, device password generation, credential storage
License
MIT