For setup and operational commands, use RUNBOOK.md.
This document describes the cryptographic key hierarchy used by SentinelID Edge, the rotation procedure, and the identity-deletion semantics.
OS Keychain / SENTINELID_MASTER_KEY env var
|
v
Master Key (AES-256, 32 bytes)
|
| HKDF-SHA256(master_key, info="sentinelid-template-v1:<template_id>", salt=<per-blob salt>)
|
v
Per-Template Key (AES-256, 32 bytes)
|
v
AES-256-GCM encrypted embedding blob in SQLite
A separate ED25519 keypair is managed by Keychain (services/security/keychain.py):
- Private key: stored in
.sentinelid/keys/device_keys.json(mode 0600) - Public key: registered with Cloud on first telemetry batch
- Device ID: SHA256(public_key)[:32] as a UUID string
Device keys are used exclusively for signing telemetry batches; they are not used for encryption.
Priority order at startup:
-
macOS Keychain (via
keyringlibrary)- Service:
com.sentinelid.edge - Account:
master_encryption_key - Value: 64 hex characters (32 bytes)
- Service:
-
Environment variable
SENTINELID_MASTER_KEY- Must be exactly 64 hex characters
- Suitable for CI and development; not recommended in production
-
Fallback file
.sentinelid/keys/master_key.hex- Created automatically when neither keychain nor env var is available
- Mode 0600 (owner read/write only)
- Used only when
keyringis unavailable
If none of these exist, a fresh key is generated and stored using the best available method.
Every face embedding is stored as an encrypted blob with the following layout:
Offset Length Field
------ ------ -----
0 4 Magic bytes: 0x53454E43 ("SENC")
4 1 Version: 0x01
5 16 Per-blob random salt (used in HKDF)
21 12 AES-GCM nonce (random per encryption)
33 N+16 AES-GCM ciphertext + 128-bit authentication tag
Additional Authenticated Data (AAD) bound to each ciphertext:
"template:<template_id>"
This ensures that a ciphertext encrypted for template A cannot be substituted for template B without detection.
Rotation replaces the master key without any downtime or data loss.
POST /api/v1/admin/rotate-key
Authorization: Bearer <EDGE_AUTH_TOKEN>
Access is restricted to localhost (127.0.0.1 / ::1).
- Generate a new 32-byte master key in memory (not persisted yet).
- For each template in the database: a. Decrypt the existing blob using the old master key. b. Re-encrypt using the new master key (new salt + nonce).
- All UPDATE statements are wrapped in a single SQLite
BEGIN EXCLUSIVEtransaction. If any step fails, the transaction is rolled back and the old key remains active. The database is never left in a partially-rotated state. - On success, persist the new key to the OS keychain (or fallback file).
- Update the in-memory key cache.
{
"status": "rotated",
"templates_rewrapped": 5,
"rotated_at": 1700000000
}curl -s -X POST http://127.0.0.1:8787/api/v1/admin/rotate-key \
-H "Authorization: Bearer $(cat .sentinelid/edge_token)" | jq .POST /api/v1/settings/delete_identity
Authorization: Bearer <EDGE_AUTH_TOKEN>
Content-Type: application/json
{
"clear_audit": true,
"clear_outbox": true,
"rotate_device_key": true
}
All fields default to true.
| Data | Controlled by | Effect |
|---|---|---|
| Face embedding templates | always | All rows deleted from templates table |
| Master encryption key | always | Removed from keychain and fallback file; future blobs unreadable |
| Audit log | clear_audit |
All rows deleted from audit_events table |
| Telemetry outbox / DLQ | clear_outbox |
All rows deleted from outbox_events table |
| Device keypair | rotate_device_key |
New ED25519 key generated; new device_id derived |
When rotate_device_key=false the keypair is regenerated on the next boot.
{
"status": "deleted",
"templates_deleted": 3,
"audit_events_deleted": 42,
"outbox_events_deleted": 7,
"device_key_rotated": true,
"deleted_at": 1700000000
}-
Forward secrecy for templates: Each blob uses a unique random salt, so re-using the same master key and template_id still produces a different derived key per blob (HKDF with unique salt).
-
Template isolation: Compromising the derived key for one template does not reveal the master key or derived keys for any other template.
-
Authenticated encryption: AES-256-GCM provides both confidentiality and integrity. Any tampering with a stored blob is detected at decryption time.
-
Key deletion is permanent: After
delete_identity, all encrypted blobs are unreadable because the master key is destroyed. There is no recovery mechanism.
- The OS user account running the edge service is trusted.
- The SQLite database file is stored on the local file system and protected by OS file permissions.
- The master key stored in the OS keychain is protected by macOS Keychain access controls (user login required to unlock).
- The environment variable fallback (
SENTINELID_MASTER_KEY) is suitable for development only. In production, rely on the OS keychain.