Replay Protection
openme uses two complementary mechanisms to prevent replay attacks — where an attacker captures a valid knock packet and re-sends it later.
Mechanism 1: Timestamp Window
Every plaintext payload contains a Unix nanosecond timestamp generated by the client at knock time. The server rejects any packet whose timestamp falls outside a configurable window (default ±60 seconds):
if |now() - packet.timestamp| > replay_window → reject
Why nanoseconds? Nanosecond precision makes two legitimate knocks sent within the same second produce different timestamps, which combined with the random nonce below makes collisions negligible.
Clock skew: Client and server clocks must be within replay_window / 2 of each other. NTP is sufficient. If you run openme in an environment with unreliable clocks, increase replay_window in the server config.
Mechanism 2: Random Nonce Cache
Each knock includes 16 bytes (128 bits) of CSPRNG output. The server maintains a short-lived cache of nonces it has seen. If the same nonce appears twice, the second packet is rejected:
if nonce ∈ seen_cache → reject
seen_cache.insert(nonce, ttl = replay_window)
The cache is pruned every replay_window / 2 seconds. Memory usage is bounded: at 16 bytes per nonce and a 60-second window, even 100,000 knocks/minute would consume under 100 MB.
Why Both Mechanisms?
| Attack | Stopped by |
|---|---|
| Replay after window expires | Timestamp check |
| Replay within the window (two fast replays) | Nonce cache |
| Clock manipulation to extend window | Nonce cache |
Neither mechanism alone is sufficient:
- The timestamp alone fails if an attacker replays within 60 seconds.
- The nonce cache alone fails if the server restarts and loses the cache (the timestamp window then acts as a backstop).
Configuration
server:
replay_window: 60s # default; increase if clocks are unreliableNonce Cache After Restart
The nonce cache is in-memory only and is lost on server restart. After a restart, an attacker who captured a packet within the previous replay_window could theoretically replay it (the nonce cache is empty, and the timestamp may still be fresh). The timestamp window provides the backstop in this case.
For high-security deployments, consider persisting the nonce cache to disk (planned feature).