How do you protect a Themis-signed request payload against replay attacks?
Explanation & Code
Answer: A signature proves who sent a payload but says nothing about when, so an attacker who captures a valid signed request can resend it verbatim and it will still verify. The fix is to put a freshness value inside the signed body — a timestamp or a server-issued nonce — and have the server reject anything stale or already seen.
Sign the JSON after injecting the timestamp, never as a separate header. A value outside the signed bytes can be rewritten by anyone in the middle, which defeats the whole mechanism. On the server, reject requests outside a tight clock window and keep a short-lived cache of recently seen nonces so a request inside the window cannot be replayed either.
Code Example:
extension Encodable {
/// Injects a timestamp into the JSON, then signs the whole body.
func signedPayload() throws -> String {
let data = try JSONEncoder().encode(self)
var dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
dict["timestamp"] = Int(Date().timeIntervalSince1970 * 1000)
let withTimestamp = try JSONSerialization.data(withJSONObject: dict)
guard let json = String(data: withTimestamp, encoding: .utf8) else {
throw CryptoError.encoding
}
return try CryptoService.sign(message: json) // TSMessage sign mode
}
}
Key Points:
- The freshness value must be inside the signed bytes, not a sibling header.
- Pair a clock-skew window with a seen-nonce cache; neither alone is sufficient.
- Device clocks drift and users change them — never trust the client timestamp as truth, only as a bound.
Rate your understanding: