Bleichenbacher Attack in Chi (Go)
Bleichenbacher Attack in Chi with Go — how this specific combination creates or exposes the vulnerability
The Bleichenbacher attack (CVE-1998-0609) exploits padding oracle vulnerabilities in RSA PKCS#1 v1.5 decryption, allowing attackers to decrypt ciphertexts or forge signatures by observing server responses to malformed inputs. In Go applications using the Chi router, this vulnerability can surface when handling RSA-decrypted values (e.g., JWTs, encrypted cookies, or API tokens) without proper constant-time validation. Chi’s middleware chain does not automatically protect against timing side-channels; if a handler uses Go’s crypto/rsa package to decrypt user-controlled input and leaks timing differences via HTTP status codes or response times, an attacker can iteratively refine ciphertexts to recover plaintext.
For example, a Chi route that decrypts an RSA-encrypted token from a header and returns 400 Bad Request on padding errors but 200 OK on valid padding creates a padding oracle. Attackers can send crafted ciphertexts and measure response timing or status to distinguish valid from invalid padding, eventually decrypting sensitive data. This is exacerbated in Go because crypto/rsa.DecryptPKCS1v15 is not constant-time by default, and Chi does not wrap handlers in timing-safe decorators. The combination of Chi’s flexible handler pattern and Go’s RSA usage creates an exploitable surface when developers assume routing frameworks implicitly secure cryptographic operations.
Go-Specific Remediation in Chi — concrete code fixes
To mitigate Bleichenbacher attacks in Chi-based Go applications, replace crypto/rsa.DecryptPKCS1v15 with constant-time alternatives and avoid exposing decryption outcomes via side-channels. Use crypto/rsa.DecryptOAEP with SHA-256, which is resistant to padding oracles, or implement constant-time validation for PKCS#1 v1.5 if legacy support is required. The following example shows a secure Chi middleware that validates RSA-OAEP decryption without leaking timing information:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"net/http"
"github.com/go-chi/chi/v5"
)
func RSAOAEPDecryptMiddleware(privateKey *rsa.PrivateKey) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
encToken := r.Header.Get("X-Encrypted-Token")
if encToken == "" {
http.Error(w, "missing token", http.StatusBadRequest)
return
}
ciphertext, err := decodeBase64URL(encToken)
if err != nil {
http.Error(w, "invalid token encoding", http.StatusBadRequest)
return
}
// Constant-time decryption using OAEP
plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, ciphertext, nil)
if err != nil {
// Do not leak error details; return generic error
http.Error(w, "invalid token", http.StatusBadRequest)
return
}
// Attach decrypted token to context for handlers
ctx := r.Context()
ctx = context.WithValue(ctx, "decryptedToken", plaintext)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func decodeBase64URL(s string) ([]byte, error) {
// Add padding if needed
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.URLEncoding.DecodeString(s)
}
func main() {
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
r := chi.NewRouter()
tr.Use(RSAOAEPDecryptMiddleware(privateKey))
tr.Get("/secure", func(w http.ResponseWriter, r *http.Request) {
token := r.Context().Value("decryptedToken").([]byte)
w.Write(token)
})
http.ListenAndServe(":8080", r)
}
Key mitigations: 1) OAEP padding prevents adaptive chosen-ciphertext attacks; 2) Error messages are generic (invalid token) to avoid oracle leakage; 3) No timing differences between error paths. For legacy PKCS#1 v1.5 use, implement constant-time validation via bitwise comparison (not shown here due to complexity; prefer OAEP). Chi’s middleware pattern enables centralized, consistent application of these controls across routes.