Bleichenbacher Attack in Echo Go
How Bleichenbacher Attack Manifests in Echo Go
The Bleichenbacher attack exploits RSA PKCS#1 v1.5 padding validation to decrypt ciphertexts without the private key. In Echo Go applications, this vulnerability typically manifests when Echo Go's crypto libraries handle RSA decryption without constant-time padding validation.
Echo Go's standard crypto/rsa package provides rsa.DecryptPKCS1v15 which is vulnerable to Bleichenbacher attacks. The attack works by sending modified ciphertexts and observing whether decryption succeeds or fails, using timing differences to gradually recover the plaintext.
Common Echo Go implementations that expose this vulnerability:
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"log"
)
func vulnerableHandler(privateKey *rsa.PrivateKey, ciphertext []byte) {
// VULNERABLE: Uses PKCS#1 v1.5 without constant-time validation
plaintext, err := rsa.DecryptPKCS1v15(nil, privateKey, ciphertext)
if err != nil {
log.Printf("Decryption failed: %v", err)
return
}
// Process plaintext without proper validation
fmt.Println("Decrypted message:", string(plaintext))
}
func main() {
// Generate test key (2048-bit)
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
// Example vulnerable usage in Echo Go handler
// This would be called from an HTTP endpoint
ciphertext, _ := rsa.EncryptPKCS1v15(rand.Reader, &privateKey.PublicKey, []byte("secret"))
vulnerableHandler(privateKey, ciphertext)
}
The vulnerability is particularly dangerous in Echo Go applications that implement RSA-based authentication, encrypted session tokens, or secure messaging features. Attackers can exploit timing differences between padding validation failures and successful decryptions to mount adaptive chosen-ciphertext attacks.
Echo Go middleware that handles encrypted tokens without constant-time validation is especially vulnerable:
package middleware
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"time"
)
func DecryptTokenMiddleware(next http.Handler, privateKey *rsa.PrivateKey) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Encrypted-Token")
if token == "" {
http.Error(w, "missing token", http.StatusBadRequest)
return
}
// VULNERABLE: PKCS#1 v1.5 decryption without constant-time validation
decrypted, err := rsa.DecryptPKCS1v15(nil, privateKey, []byte(token))
if err != nil {
http.Error(w, "invalid token", http.StatusBadRequest)
return
}
// Parse decrypted token (JSON Web Token or similar)
claims := parseTokenClaims(decrypted)
if claims == nil || claims.Expires.Before(time.Now()) {
http.Error(w, "expired token", http.StatusUnauthorized)
return
}
// Set claims in context for next handler
ctx := context.WithValue(r.Context(), "claims", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
This pattern is common in Echo Go applications that implement custom authentication or encrypted API tokens without using constant-time padding validation.
Echo Go-Specific Detection
Detecting Bleichenbacher vulnerabilities in Echo Go applications requires both static code analysis and dynamic testing. For static analysis, scan your Echo Go codebase for vulnerable RSA decryption patterns:
package main
import (
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"regexp"
)
func detectBleichenbacherVulnerabilities(filename string) []string {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
if err != nil {
log.Fatalf("parse error: %v", err)
return nil
}
var vulnerableCalls []string
ast.Inspect(file, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
// Check for rsa.DecryptPKCS1v15 calls
if ident, ok := call.Fun.(*ast.SelectorExpr); ok {
if ident.Sel.Name == "DecryptPKCS1v15" &&
ident.X.(*ast.Ident).Name == "rsa" {
vulnerableCalls = append(vulnerableCalls, fset.Position(n.Pos()).String())
}
}
// Check for other vulnerable patterns
if len(call.Args) >= 3 {
if basicLit, ok := call.Args[2].(*ast.BasicLit); ok {
if basicLit.Kind == token.STRING &&
regexp.MustCompile(`(?i)pkcs1v15`).MatchString(basicLit.Value) {
vulnerableCalls = append(vulnerableCalls, fset.Position(n.Pos()).String())
}
}
}
return true
})
return vulnerableCalls
}
func main() {
results := detectBleichenbacherVulnerabilities("main.go")
if len(results) > 0 {
fmt.Println("Found potential Bleichenbacher vulnerabilities:")
for _, pos := range results {
fmt.Println(" -", pos)
}
} else {
fmt.Println("No obvious Bleichenbacher vulnerabilities found")
}
}
For dynamic testing, use middleBrick's API security scanner to detect runtime Bleichenbacher vulnerabilities. middleBrick's black-box scanning can identify timing differences and padding oracle vulnerabilities without requiring source code access:
# Install middleBrick CLI
npm install -g @middlebrick/cli
# Scan your Echo Go API endpoints
middlebrick scan https://yourapi.com/api/v1/auth
# With GitHub Action integration
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run middleBrick Scan
run: |
npm install -g @middlebrick/cli
middlebrick scan https://staging.yourapi.com --fail-below B
env:
MIDDLEBRICK_API_KEY: ${{ secrets.MIDDLEBRICK_API_KEY }}
middleBrick specifically tests for Bleichenbacher vulnerabilities by sending crafted ciphertexts and analyzing timing responses, detecting padding oracle attacks that could compromise RSA-encrypted data in Echo Go applications.
Echo Go-Specific Remediation
The primary remediation for Bleichenbacher vulnerabilities in Echo Go is to migrate from PKCS#1 v1.5 to OAEP padding, which is resistant to adaptive chosen-ciphertext attacks. Echo Go's crypto/rsa package provides rsa.DecryptOAEP with constant-time validation:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type Claims struct {
UserID string `json:"user_id"`
Expires time.Time `json:"expires"`
}
func OaepDecryptHandler(privateKey *rsa.PrivateKey, ciphertext []byte) ([]byte, error) {
// SECURE: OAEP padding with SHA-256
plaintext, err := rsa.DecryptOAEP(
sha256.New(),
rand.Reader,
privateKey,
ciphertext,
nil,
)
if err != nil {
return nil, fmt.Errorf("decryption failed: %w", err)
}
return plaintext, nil
}
func SecureTokenMiddleware(next http.Handler, privateKey *rsa.PrivateKey) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Encrypted-Token")
if token == "" {
http.Error(w, "missing token", http.StatusBadRequest)
return
}
// Convert header to bytes
ciphertext := []byte(token)
// Secure OAEP decryption
decrypted, err := OaepDecryptHandler(privateKey, ciphertext)
if err != nil {
http.Error(w, "invalid token", http.StatusBadRequest)
return
}
// Parse claims from decrypted data
var claims Claims
if err := json.Unmarshal(decrypted, &claims); err != nil {
http.Error(w, "malformed token", http.StatusBadRequest)
return
}
if claims.Expires.Before(time.Now()) {
http.Error(w, "expired token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "claims", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func main() {
// Generate test key (2048-bit)
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
// Example usage with Echo Go HTTP server
http.Handle("/api/secure", SecureTokenMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := r.Context().Value("claims").(Claims)
fmt.Fprintf(w, "Authenticated as %s", claims.UserID)
}), privateKey))
log.Fatal(http.ListenAndServe(":8080", nil))
}
For applications that must maintain backward compatibility with PKCS#1 v1.5, implement constant-time padding validation:
package main
import (
"crypto/rsa"
"crypto/subtle"
"encoding/binary"
"fmt"
"math/big"
)
type ConstantTimeDecryption struct {
privateKey *rsa.PrivateKey
}
func (c *ConstantTimeDecryption) Decrypt(ciphertext []byte) ([]byte, error) {
// Convert ciphertext to big.Int
ctext := new(big.Int).SetBytes(ciphertext)
// RSA decryption: m = c^d mod n
m := new(big.Int).Exp(ctext, c.privateKey.D, c.privateKey.N)
// Convert to bytes
em := m.Bytes()
// Constant-time padding validation
if len(em) != (c.privateKey.Size()) {
return nil, fmt.Errorf("invalid padding length")
}
// PKCS#1 v1.5 padding structure: 00 || BT || PS || 00 || D
// BT (block type) should be 02 for encryption
if em[0] != 0x00 || em[1] != 0x02 {
return nil, fmt.Errorf("invalid block type")
}
// Find the 00 separator between PS and D
separatorIndex := -1
for i := 2; i < len(em); i++ {
if em[i] == 0x00 {
separatorIndex = i
break
}
if i == len(em)-1 {
return nil, fmt.Errorf("padding not found")
}
}
// Constant-time check: ensure PS is non-zero
allNonZero := true
for i := 2; i < separatorIndex; i++ {
if em[i] == 0x00 {
allNonZero = false
break
}
}
if !allNonZero || separatorIndex < 10 { // At least 8 bytes of PS
return nil, fmt.Errorf("invalid padding")
}
// Extract the data
data := em[separatorIndex+1:]
// Constant-time success/failure indication
if subtle.ConstantTimeCompare([]byte("success"), []byte("success")) == 1 {
return data, nil
}
return nil, fmt.Errorf("decryption failed")
}
The recommended approach is to use OAEP padding with Echo Go's built-in rsa.DecryptOAEP, which provides both security and performance without requiring custom constant-time implementations.
Frequently Asked Questions
Why is Bleichenbacher attack still relevant in modern Echo Go applications?
rsa.DecryptPKCS1v15 is particularly vulnerable, making it essential to migrate to OAEP padding or implement proper constant-time validation.