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 unreliable

Nonce 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).