crowdsecurity/crowdsec · warning

failed to decode obfuscated fingerprint: %w

Error message

failed to decode obfuscated fingerprint: %w

What it means

deobfuscateFingerprint base64-decodes the client-supplied fingerprint blob (repeating-key XOR + base64 applied client-side) before validating the challenge response. This error means the payload is not valid standard base64, i.e. the client sent malformed or tampered fingerprint data.

Source

Thrown at pkg/appsec/challenge/ticket.go:161

			return false
		}
	}

	return true
}

// deriveFingerprintObfKey returns the keystream key for the fingerprint
// payload: `HMAC(s, "fpenc"||r)`.
func deriveFingerprintObfKey(s, r string) string {
	return hmacSHA256Hex([]byte(s), []byte("fpenc"+r))
}

// deobfuscateFingerprint reverses the client-side repeating-key XOR + base64
// applied to the fingerprint JSON, using the key from deriveFingerprintObfKey.
func deobfuscateFingerprint(obfKey string, payload string) (string, error) {
	payloadBytes, err := base64.StdEncoding.DecodeString(payload)
	if err != nil {
		return "", fmt.Errorf("failed to decode obfuscated fingerprint: %w", err)
	}

	out := make([]byte, len(payloadBytes))

	for i := range payloadBytes {
		out[i] = payloadBytes[i] ^ obfKey[i%len(obfKey)]
	}

	return string(out), nil
}

// verifyChallenge gates timestamp freshness and authenticates the PoW-salt
// binding, returning the per-epoch sign key (for deriving `s`) on success.
// Stateless — any instance sharing the master secret can verify. Knowledge of
// the per-epoch key is proven separately by the caller's `sig` check.
func (c *ChallengeRuntime) verifyChallenge(clientR, clientTS, clientPowSalt, clientPowMAC string, clientDifficulty int) ([]byte, bool) {
	tsVal, err := strconv.ParseInt(clientTS, 10, 64)
	if err != nil || tsVal <= 0 {

View on GitHub (pinned to 909b515798)

Solutions

  1. Check that the client-side challenge script and crowdsec appsec component are the same version (obfuscation key/scheme must match)
  2. Log the raw payload and verify it is valid standard base64 (padding, charset)
  3. Rule out proxies/WAFs rewriting the request body (re-encoding, chunk mangling)
  4. If you wrote the client, confirm you XOR with the same key and use StdEncoding (not URL/raw encoding) before sending

Example fix

// before (client, JavaScript)
const payload = btoa(xored)
// after — use URL-safe-free standard base64 matching Go StdEncoding, ensure binary-safe XOR bytes
const payload = btoa(String.fromCharCode(...xoredBytes))
Defensive patterns

Strategy: validation

Validate before calling

if _, err := base64.StdEncoding.DecodeString(payload); err != nil {
    // reject before calling validation
}

Try / catch

fp, err := deobfuscateFingerprint(obfKey, payload)
if err != nil {
    log.Debugf("bad fingerprint payload: %v", err)
    return challengeFailed
}

Prevention

When it happens

Trigger: ValidateChallengeResponse receives a fingerprint payload that fails base64.StdEncoding.DecodeString — the bouncer/client sent a corrupted, truncated, or non-base64 obfuscated fingerprint, or encrypted it with a different scheme than the server expects.

Common situations: Version mismatch between client-side challenge JS/bouncer and server (obfuscation scheme changed), intermediaries mangling the POST body, or a malicious/broken client submitting garbage instead of the computed fingerprint.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/dfb6a16724f8c582. Report an issue: GitHub.