Skip to content

Latest commit

Β 

History

102 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›‘οΈ SentryKey

Your 2FA codes, on your wrist.

SentryKey is a self-contained TOTP (two-factor authentication) authenticator for Garmin watches, with companion apps for Android and iOS. Scan your 2FA QR codes on your phone, sync them to your watch over Bluetooth, and read your rotating codes right from your wrist β€” no phone required after sync.

Build Latest Release Connect IQ Store Platform Monkey C Kotlin Swift


πŸ§ͺ Beta testers wanted!

I'm looking for testers for the SentryKey Android app β€” now in closed beta on Google Play. It's free; try it out, kick the tires, and tell me what breaks or feels confusing.

Become a tester β€” 3 quick steps:

  1. Join the testers group: groups.google.com/g/sentrykey-testers β†’ click Join group
  2. Opt in on Google Play: play.google.com/apps/testing/com.fennell.sentrykey β†’ tap Become a tester
  3. Install SentryKey from the Play Store and give it a spin πŸ™Œ

⚠️ Do both steps 1 and 2 β€” joining the group alone doesn't opt you in, and the Play opt-in only works once you're a group member.

Hit a bug or have feedback? Open an issue β€” it genuinely helps. πŸ™

Prefer to sideload? Grab the APK from the latest release.


✨ Features

  • ⌚ Standalone watch authenticator β€” RFC 6238 TOTP codes generated on-device, even with no phone nearby.
  • πŸ” Fully self-contained crypto β€” Base32, HMAC-SHA1, and SHA-1 implemented in pure Monkey C (no flaky native dependency).
  • πŸ“· QR scanning β€” add accounts by scanning otpauth:// QR codes on your phone.
  • πŸ”„ One-tap Bluetooth sync β€” push your vault from phone to watch via the Garmin Connect IQ SDK.
  • 🎨 Premium dark UI β€” black background, brand-orange countdown ring that turns red in the final 5 seconds, auto-fitting account labels, and multi-account page dots.
  • πŸ—„οΈ Persistent & secure β€” vault survives watch restarts; phone secrets are encrypted at rest (Android Keystore AES-256-GCM / iOS Keychain).
  • πŸ”‘ Encrypted backups β€” export a passphrase-locked backup (PBKDF2 + AES-256-GCM) that's useless without the passphrase; plaintext export still available with a warning.

🧩 Architecture

   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚   Android companion β”‚        β”‚    iOS companion    β”‚
   β”‚   (Kotlin + Compose)β”‚        β”‚   (Swift + SwiftUI) β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚   "label:secret,label:secret"  β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚  Connect IQ SDK (BLE)
                              β–Ό
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚  Garmin watch app    β”‚
                   β”‚     (Monkey C)       β”‚
                   β”‚  β€’ Base32 decode     β”‚
                   β”‚  β€’ HMAC-SHA1 / SHA-1 β”‚
                   β”‚  β€’ TOTP every 30s    β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Both companion apps serialize the vault to a simple label:secret,label:secret string and send it to the watch, which parses, stores, and computes codes locally. The watch never needs the phone again until you add a new account.


πŸ“‚ Repository layout

Path What it is
source/ Garmin watch app (Monkey C)
resources/ Watch strings, settings, drawables
manifest.xml Watch app manifest (UUID, products, permissions)
companion/ Android companion (Kotlin + Jetpack Compose)
companion-ios/ iOS companion (Swift + SwiftUI)
.github/workflows/build.yml CI: build all targets + publish releases

⌚ Supported watches

Runs on 100+ round Garmin watches (Connect IQ API 3.2+) β€” the whole UI is resolution-independent (proportional countdown ring, round-screen-aware labels, auto-scaling fonts), so it adapts from 218px to 454px screens. Square/rectangular devices are intentionally excluded (the ring UI is built for round faces).

Families covered include fenix (5 Plus β†’ 8), epix, Forerunner (55/165/2xx/9xx…), venu (1/2/3/441/445), vivoactive (3β†’6), Instinct (2/3/Crossover/E), MARQ (1/2), Descent, Enduro, D2, Approach S7x, and the tactix / legacy hero series.

The full device list is in manifest.xml; add or remove products there.


πŸš€ Building

Garmin watch app

Requires the Connect IQ SDK and a developer key.

# Build for a specific device
./build.ps1 -Device fenix8solar51mm

# Run in the Connect IQ simulator
./build.ps1 -Device fenix8solar51mm -Run

# Package a store .iq
./build.ps1 -Export

Android companion

The Android app ships in two distribution flavors that differ only in how they self-update:

Flavor Update mechanism Goes to
play Google Play In-App Updates (no install permission) Google Play
github Self-installs the APK from GitHub Releases (sideload) GitHub Releases
cd companion
./gradlew assembleGithubDebug   # sideload build β†’ app/build/outputs/apk/github/debug/
./gradlew assemblePlayDebug     # Play build (uses Play In-App Updates)

Only the github flavor carries the REQUEST_INSTALL_PACKAGES permission, so the Play upload stays clean. CI builds and publishes the github flavor to GitHub Releases.

iOS companion

Requires macOS + Xcode. See companion-ios/README.md for full setup (the Connect IQ Mobile SDK framework + URL-scheme handshake).


πŸ” Releases

Every push of a v* tag triggers CI to build the watch binaries (all targets), the Android APK, and publish them as a GitHub Release.

git tag -a v1.0.0-beta.x -m "release notes"
git push origin v1.0.0-beta.x

πŸ”’ Security notes

  • TOTP secrets are sensitive. On Android the vault is encrypted at rest with an AES-256-GCM key held in the Android Keystore (hardware-backed where the device supports it); on iOS they're stored in the Keychain. On the watch they live in app Storage in plaintext (Connect IQ has no keystore).
  • Backups can be exported encrypted: the passphrase is stretched with PBKDF2-HMAC-SHA256 (210k iterations) and the vault sealed with AES-256-GCM. There's no passphrase recovery β€” lose it and the backup is gone. A plaintext JSON/otpauth export is still available behind an explicit warning for moving accounts to other authenticators.
  • The label:secret BLE sync string can optionally be encrypted end-to-end: set a sync passphrase in the phone app and the same passphrase in the watch's Connect IQ settings. The payload is then sealed with an HMAC-SHA1-based passphrase scheme (PBKDF2 β†’ keystream + MAC, marker SKENC1:) that all three platforms implement from the same primitive. With no passphrase set, sync stays plaintext (and should be treated as secret material). This is defense-in-depth over an already-link-encrypted BLE channel, not an audited protocol.
  • This is a personal/hobby project, not a security-audited product. Keep a backup of your 2FA secrets elsewhere.

πŸ—ΊοΈ Roadmap

  • Comma-safe label sanitization in the sync string
  • Issuer-only label display option on the watch
  • iOS TestFlight distribution

❀️ Support

SentryKey is free forever β€” no paid tiers, no subscriptions, no ads. If it's useful to you, a donation funds new features and keeps the managed cloud at sentrykey.app running:

Not a donor? Starring the repo and filing good bug reports helps just as much.


Made with ⚑ for the wrist.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages