A minimalist, secure, and aesthetic TOTP authenticator for Android and Wear OS.
aniAuth is a local-first, privacy-focused 2FA authenticator combining aesthetic UI designs with hardware-backed encryption to secure your accounts on your phone and watch.
Note
For release logs, see CHANGELOG.md. All details regarding the Wear OS companion app (features, setup, security, and codebase structure) can be found in WEAROS.md.
- Features
- Wear OS Companion App
- Security and Threat Model
- Codebase Structure
- Compatibility and Imports
- How It Works Under the Hood
- Build and Installation
- Contact and Feedback
- License
- High-Speed QR Scanner: Built using Google ML Kit Vision API. Uses a dynamic thin client via Google Play Services to keep the APK build size and post-installation footprint extremely lightweight.
- Manual Entry: Polished form with field validation for adding keys with custom labels and names.
- Hardware-Backed Encryption: Master encryption keys are stored securely within the device's Secure Element (SE) or Trusted Execution Environment (TEE) via the Android KeyStore.
- Biometric & Device Lock: Optional biometric prompt (fingerprint or face unlock) required on app startup, with native device credentials (PIN/pattern/password) fallback to prevent lockouts.
- Zero Network Footprint: Fully offline. The app requests no internet permission whatsoever. (Note: Google Play Services handles ML model downloads at the OS level, keeping the app completely sandboxed from the network).
- Biometric Syncing: Wirelessly transfer accounts from phone to watch via Bluetooth, protected by the phone's biometric verification.
- Customizable Duress PIN: Configure the maximum allowed PIN attempts (e.g., 3, 6, 9, or unlimited) on your watch directly from the phone app. If the limit is exceeded, all watch accounts are silently wiped to protect your data.
- Aesthetic Light & Dark Themes: Modern, high-contrast themes built with Jetpack Compose Material 3. Features a premium "Obsidian-Violet" dark mode, and a sleek, high-readability light mode.
- Dynamic Theme Selector: Choose between Light Mode, Dark Mode, or System Default dynamically in the settings panel.
- Persistent Account Sorting: Sort accounts dynamically by Date Added, Alphabetical (A-Z), or Alphabetical (Z-A), with your selection automatically saved.
- Dynamic Alphabet Scroll Overlay: Sleek fast-scroll sidebar on the right margin with elastic letter magnification, tactile feedback clicks, and a floating preview bubble that follows your finger. Automatically stays faint and out of focus when idle, lighting up dynamically when you scroll or hover to keep the dashboard clutter-free.
- Dedicated Settings Screen: Access theme configurations, data management (encrypted backups/imports), biometric security toggles, policy details, and guides in one centralized screen.
- Comprehensive User Manual & Wear OS Sync Guide: In-app guide detailing quick controls, single global timer sync, TEE KeyStore encryption, password-protected backups, and Wear OS Bluetooth sync & offline security mechanics.
- Integrated Search & Timer: Unified search bar and countdown timer in a sleek pill header. Displays a soft-toned refresh message that collapses into an active search field with automatic keyboard focus on click.
- One-Tap Copy: Tap any card to copy the code directly to your clipboard.
- Flexible Backup Exports: Export backups in secure Encrypted format (AES-256 encrypted with aniAuth's internal key) or Decrypted format (plaintext JSON) with built-in warnings.
- Account Management: Long-press any card to securely view decrypted secret keys, edit account metadata, or delete credentials.
- Adaptive Launcher Icon: Creative geometric "A" icon with glowing arches designed for modern home screen styling.
All account data is stored locally in SharedPreferences. The raw shared secrets are encrypted using the AES/GCM/NoPadding cipher.
- Key Generation: A 256-bit AES master key is generated inside the Android KeyStore using the
KeyGenParameterSpecbuilder with GCM block mode and no padding. - Key Isolation: The master key remains isolated in hardware (TEE/SE) and cannot be extracted in plain text by the operating system or other apps.
- Payload Encryption: For each account, the secret is encrypted with a unique initialization vector (IV). The IV (12 bytes) and cipher text are combined, Base64-encoded, and saved to disk.
When exporting backups, data security is maintained through a combination of key derivation and GCM encryption:
- Key Derivation (KDF): A strong 256-bit AES key is derived from the backup password using PBKDF2WithHmacSHA256 with 10,000 iterations and a cryptographically secure 16-byte salt.
- Encryption: The JSON payload (with decrypted secrets) is encrypted with the derived key using AES/GCM/NoPadding and a secure 12-byte IV.
- Export Payload: The exported file is formatted as a Base64 string containing:
salt (16 bytes) + IV (12 bytes) + encrypted payload.
aniAuth/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/aniauth/authenticator/
│ │ │ │ ├── crypto/
│ │ │ │ │ ├── BackupManager.kt # Encrypted backup & import engine
│ │ │ │ │ ├── KeyStoreHelper.kt # Hardware-backed AES encryption (key: AniAuthMasterKey)
│ │ │ │ │ ├── OtpAuthParser.kt # Parse otpauth:// URIs
│ │ │ │ │ └── TotpGenerator.kt # RFC 6238 TOTP calculator
│ │ │ │ ├── model/
│ │ │ │ │ ├── Account.kt # Data model representing a 2FA account
│ │ │ │ │ ├── AccountRepository.kt # Local storage and CRUD operations
│ │ │ │ │ └── AccountSerializer.kt # JSON serialization for backup/import
│ │ │ │ ├── ui/
│ │ │ │ │ ├── screens/
│ │ │ │ │ │ ├── AddAccountScreen.kt
│ │ │ │ │ │ ├── BiometricLockScreen.kt
│ │ │ │ │ │ ├── DashboardScreen.kt
│ │ │ │ │ │ ├── ScannerScreen.kt
│ │ │ │ │ │ ├── AccountDetailsScreen.kt
│ │ │ │ │ │ └── SettingsScreen.kt
│ │ │ │ │ └── theme/
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ └── MainActivity.kt # App lifecycle & entry point
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── wearos/ # Wear OS companion module
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/aniauth/authenticator/wearos/
│ │ │ │ ├── crypto/
│ │ │ │ │ ├── KeyStoreHelper.kt # Watch-local AES encryption (key: AniAuthWatchMasterKey)
│ │ │ │ │ └── TotpGenerator.kt # Standalone RFC 6238 TOTP calculator
│ │ │ │ ├── model/
│ │ │ │ │ ├── Account.kt # Watch account data model
│ │ │ │ │ └── WatchRepository.kt # Watch-local storage with decryption cache
│ │ │ │ ├── sync/
│ │ │ │ │ └── WearSyncService.kt # Wearable Data Layer message listener
│ │ │ │ └── MainActivity.kt # Watch UI: PIN lock, keypad, dashboard
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── build.gradle.kts
├── settings.gradle.kts
├── CHANGELOG.md
├── WEAROS.md # Wear OS companion documentation
├── LICENSE
└── README.md
aniAuth makes migrating from other password managers and authenticators seamless by offering smart one-way imports:
- Bitwarden Vault Exports: Import Bitwarden's JSON vaults directly. The app will extract TOTP secrets from the standard
login.totpfield nested inside items. - Universal Parser: The importer automatically parses common properties like
secret,key,encryptedSecret,label,name,issuer, andusernameto build the credentials list. - Double-Encryption Safety: On import, plain text secrets are parsed, encrypted via the device's hardware KeyStore immediately, and then written to the database.
When you add an account (via QR scan, manual entry, or import), the following happens:
- Secret Extraction: The raw Base32 secret is extracted from the
otpauth://URI (viaOtpAuthParser) or from the manual entry form. - Immediate Encryption: The plaintext secret is passed to
KeyStoreHelper.encrypt()before it ever touches disk. A 256-bit AES master key (alias:AniAuthMasterKey) stored inside the Android KeyStore's TEE/SE hardware enclave performs AES-GCM encryption. The system generates a cryptographically random 12-byte IV for each encryption operation. - Combined Payload: The IV (12 bytes) and ciphertext are concatenated into a single byte array and Base64-encoded (using
Base64.NO_WRAP). - Disk Write: The Base64-encoded blob is saved as the
encryptedSecretfield inside a JSON array stored inSharedPreferences(file:ani_auth_prefs). The plaintext secret is never written to disk. - Decryption Cache: On read,
KeyStoreHelper.decrypt()uses a thread-safeConcurrentHashMapto cache previously decrypted values in memory only. This avoids repeated hardware KeyStore calls during UI scrolls and recompositions, keeping the dashboard at a smooth 60fps.
The TotpGenerator implements the standard TOTP algorithm:
- The Base32-encoded secret is cleaned (whitespace/hyphens stripped, uppercased) and decoded into raw bytes using a custom Base32 decoder.
- The current Unix epoch (seconds) is divided by the time interval (default: 30 seconds) to compute the current time step.
- The time step is packed into an 8-byte big-endian
ByteBuffer. - An HMAC-SHA1 hash is computed over the time step bytes using the decoded secret as the MAC key.
- Dynamic truncation: The last 4 bits of the 20-byte HMAC hash determine an offset. A 4-byte segment is extracted starting at that offset, with the high bit masked off (
& 0x7f). - The 31-bit integer is reduced to 6 digits via
binary % 1,000,000and zero-padded withString.format("%06d", otp).
aniAuth offers two export formats, both requiring biometric/device credential verification first:
AccountSerializer.toJson(accounts, decryptSecrets = true)decrypts every account secret from the KeyStore so the backup contains portable, plaintext Base32 keys.- The JSON payload is encrypted by
BackupManager.encrypt()using a hardcoded internal backup password:- A 16-byte cryptographic salt is generated via
SecureRandom. - A 256-bit AES key is derived from the password using PBKDF2WithHmacSHA256 with 10,000 iterations.
- A 12-byte IV is generated via
SecureRandom. - The JSON is encrypted with AES/GCM/NoPadding using a 128-bit authentication tag.
- A 16-byte cryptographic salt is generated via
- The output file contains:
Base64(salt[16] + IV[12] + ciphertext).
- The same
toJson(decryptSecrets = true)call produces portable JSON. - The raw JSON is written directly to the
.jsonfile with no additional encryption.
The AccountSerializer.fromJson() parser handles multiple formats:
- aniAuth native format: Direct JSON array of
{id, label, encryptedSecret, username}objects. - Bitwarden Vault exports: Detects
items[]→login.totpnested structure, parsesotpauth://URIs viaOtpAuthParser. - Universal fallback: Searches for common field names (
secret,key,encryptedSecret,name,label,issuer,username) and auto-extracts credentials. - Re-encryption: On import,
MainActivityvalidates each secret viaTotpGenerator.isValidSecret(), encrypts valid keys with the device's KeyStore, and skips invalid/un-decodable entries (reporting the skip count to the user).
For full details on the watch companion module, see WEAROS.md.
- The phone app queries
Wearable.getNodeClient()to detect connected watches. The "Sync to Watch" settings row only appears when a paired node is found. - On tap, biometric authentication is required. After verification,
AccountSerializer.toJson(accounts, decryptSecrets = true)produces a JSON payload with plaintext Base32 secrets. - The payload is transmitted via
Wearable.getMessageClient().sendMessage()over the/sync-accountspath through the Bluetooth data layer. - On the watch,
WearSyncService(aWearableListenerService) receives the message, validates each secret viaTotpGenerator.isValidSecret(), re-encrypts each key using the watch's own independent KeyStore master key (alias:AniAuthWatchMasterKey), and saves the encrypted accounts to the watch's localSharedPreferences. - The watch then generates TOTP codes independently using its own local clock and its own copy of
TotpGenerator— no phone connection required after the initial sync.
Skip compiling from source and grab the latest pre-compiled packages directly:
- 📱 Android Phone App: aniAuth-phone-v1.3.0.apk
- ⌚ Wear OS Companion App: aniAuth-wear-v1.0.0.apk — (See Watch Installation & Sideloading Guide)
- JDK 17 or higher
- Android SDK (API 26+)
- Android Studio Koala (or newer) or command-line tools
- Clone the repository:
git clone https://github.com/anishcreations/aniAuth.git cd aniAuth - Build the Debug APK:
./gradlew assembleDebug
- Install the APK on a connected device/emulator:
./gradlew installDebug
If you encounter bugs, have feature suggestions, or want to share feedback:
- Email: Contact us at anish.creations.hq@gmail.com. Please keep the prefilled subject line intact.
- Website: You can also contact me directly via anisharyal09.com.np (for any support, features, or bugs).
- GitHub Issues: You can report issues, request features, or submit pull requests directly on the repository's issues page.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
