Phone Recorder - Architecture Overview
Purpose
Android app that records audio continuously, encrypts recordings immediately (never stored unencrypted), and syncs them to a server over LAN or internet.
Security Model
Phone (has: server public key) Server (has: private key)
───────────────────────────── ────────────────────────
Record audio → temp.ogg
Encrypt with public key →
recording_*.ogg.enc
│
└──── HTTP POST ────────→ Receive encrypted file
Decrypt with private key
Store as .ogg in recordings/
- Sealed box encryption: X25519 key exchange + XSalsa20-Poly1305
- Forward secrecy: Each encryption uses ephemeral keys
- Phone never stores unencrypted: Temp file deleted immediately after encryption
- If no public key configured: Recordings are deleted (security-first design)
File Format (SREC)
Encrypted files use a chunked format for streaming large files:
[4 bytes: "SREC" magic]
[1 byte: version (1)]
[4 bytes: chunk count, big-endian]
For each chunk (64KB max):
[4 bytes: encrypted length, big-endian]
[N bytes: sealed box ciphertext]
Android App Structure
Main Files
| File |
Purpose |
MainActivity.kt |
UI, settings, sync controls |
RecordingService.kt |
Foreground service for recording, encryption on stop |
StorageHelper.kt |
File paths, temp vs recordings directories |
Crypto Package (crypto/)
| File |
Purpose |
CryptoHelper.kt |
SREC format encryption using lazysodium |
KeyManager.kt |
Store/retrieve server public key (SharedPreferences) |
Sync Package (sync/)
| File |
Purpose |
SyncSettings.kt |
Sync mode enum, server URLs, LAN subnet prefix |
NetworkHelper.kt |
Detect WiFi, LAN (by IP prefix), network availability |
SyncTracker.kt |
Track which files synced successfully (SharedPreferences) |
SyncWorker.kt |
WorkManager background job for uploads |
SyncManager.kt |
Schedule periodic (15min) and immediate syncs |
API Package (api/)
| File |
Purpose |
RecordingsApi.kt |
OkHttp client, upload with SHA-256 verification |
Sync Modes
| Mode |
Behavior |
| Never |
No sync, files stay on phone |
| LAN Only |
Sync only when IP matches configured subnet (e.g., 10.0.0.) |
| WiFi Only |
Sync on any WiFi network |
| Any Network |
Sync on WiFi or mobile data |
Server Structure (Python/FastAPI)
| File |
Purpose |
main.py |
FastAPI endpoints |
crypto.py |
PyNaCl decryption, keypair management |
config.py |
Paths, ports |
Endpoints
| Endpoint |
Method |
Description |
/health |
GET |
Returns {"status": "ok"} |
/api/v1/public-key |
GET |
Returns Base64 public key |
/api/v1/recordings/upload |
POST |
Upload encrypted file, returns hash for verification |
/api/v1/recordings |
GET |
List decrypted recordings |
Data Flow
- Recording starts:
RecordingService writes to temp/recording_TIMESTAMP.ogg
- Recording stops:
CryptoHelper.encryptFile() reads temp, writes recordings/recording_TIMESTAMP.ogg.enc
- Temp file deleted
SyncManager.scheduleImmediateSync() triggered
- Sync runs:
SyncWorker checks network conditions match SyncMode
- Computes SHA-256 of encrypted file
- Uploads to server
- Server decrypts, returns hash
- If hashes match, marks file as synced
- Server storage:
recordings/YYYY/MM/DD/recording_TIMESTAMP.ogg
Key Dependencies
Android
lazysodium-android - libsodium bindings
androidx.work - Background job scheduling
okhttp3 - HTTP client
Server
fastapi + uvicorn - Web framework
pynacl - libsodium bindings
aiofiles - Async file I/O
Configuration
Android (in-app settings)
- Sync mode (Never/LAN/WiFi/Any)
- LAN subnet prefix (default:
10.0.0.)
- Server URLs (LAN and remote)
- Server public key (Base64)
Server (config.py or env vars)
HOST - Bind address (default: 0.0.0.0)
PORT - Listen port (default: 8000)
BASE_DIR - Base directory for keys and recordings