Trying to secure our Kotlin backend against replay attacks by verifying the X-Genesys-Signature header on incoming webhooks. The docs mention HMAC-SHA256, but I’m stuck on the exact verification logic.
Here’s what I’m doing so far:
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64
fun verifySignature(payload: String, signature: String, secret: String): Boolean {
val mac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256")
mac.init(secretKey)
val bytes = mac.doFinal(payload.toByteArray(Charsets.UTF_8))
val calculatedSignature = Base64.getEncoder().encodeToString(bytes)
return calculatedSignature == signature
}
The problem is that calculatedSignature never matches signature from the header. I’m logging both values and they look completely different. I’m assuming the payload string I’m hashing isn’t the raw body. Should I be hashing the raw bytes from the HttpServletRequest input stream before converting to a String? Or is there a specific encoding issue with the base64 output?
Also, how do I handle the timestamp check? I see X-Genesys-Timestamp in the headers. Do I need to include that in the HMAC calculation or just check it separately to ensure the request isn’t older than 5 minutes?