Bleichenbacher Attack in Actix with Cockroachdb

Bleichenbacher Attack in Actix with Cockroachdb — how this specific combination creates or exposes the vulnerability

A Bleichenbacher attack is a cryptographic padding oracle attack that exploits adaptive chosen-ciphertext to gradually decrypt RSA-encrypted data without the private key. In an Actix web service that uses CockroachDB as the backend datastore, the combination of RSA-encrypted tokens or session identifiers, error messages that distinguish padding failures from other errors, and CockroachDB-stored user secrets can create a practical oracle path.

Consider an Actix endpoint that receives an RSA-encrypted cookie or Authorization header, decrypts it using a private key on the server, and then queries CockroachDB to look up the associated user or session. If the decryption step returns distinct errors for invalid padding versus valid-but-wrong data, an attacker can send many modified ciphertexts and observe differences in HTTP responses or timing. CockroachDB itself does not introduce the padding oracle, but it can amplify risk when error handling leaks information about decryption success before a SQL query is issued, or when SQL errors (e.g., missing rows) are surfaced in a way that correlates with decryption outcomes.

For example, an Actix route might decrypt a token, extract a user ID, and run a CockroachDB query like SELECT username, role FROM users WHERE id = $1. If the decryption fails early due to bad padding, the server might return a 400 Bad Request with a message like “invalid token”. If decryption succeeds but the user ID is not found in CockroachDB, the server might return 404 Not Found. An attacker can use these differences to iteratively recover the plaintext, especially when the same key is used across sessions or when nonces/IVs are mishandled.

Insecure practices that heighten exposure include logging full decryption errors, exposing stack traces, or including CockroachDB driver errors in HTTP responses. Even though the vulnerability originates in the cryptographic handling and not in CockroachDB itself, the database can become a side channel if error responses or timing behavior vary based on whether a user record exists or whether decryption succeeded.

To detect this class of issue, an API security scan should verify that decryption errors are normalized, that SQL errors are not informative, and that timing differences are minimized. middleBrick’s LLM/AI Security checks include active prompt injection testing and system prompt leakage detection, but for cryptographic flows it is important to complement automated scans with code review focused on constant-time decryption and uniform error handling.

Cockroachdb-Specific Remediation in Actix — concrete code fixes

Remediation centers on ensuring that decryption failures do not leak information and that CockroachDB interactions do not amplify timing or error-based side channels. Below are concrete patterns for Actix with Rust and the CockroachDB driver (e.g., cockroachdb-rs or sqlx with CockroachDB).

1. Use constant-time decryption and uniform error responses

Always attempt decryption with a constant-time library and return the same HTTP status and body shape regardless of whether the error is a padding failure, invalid token, or missing user. Avoid branching on decryption details that can be observed externally.

use actix_web::{web, HttpResponse, Result};
use rsa::{PaddingScheme, PublicKey, RsaPrivateKey};
use subtle::ConstantTimeEq;

async fn decrypt_token_constant(encrypted: &[u8], key: &RsaPrivateKey) -> Result, ()> {
    let padding = PaddingScheme::new_pkcs1v15_encrypt();
    // Use a constant-time decode path; avoid returning early on parse errors.
    let mut decrypted = vec![0u8; key.size() as usize];
    let res = key.decrypt(padding, encrypted, &mut decrypted);
    match res {
        Ok(n) => {
            decrypted.truncate(n);
            Ok(decrypted)
        }
        Err(_) => Err(()),
    }
}

async fn handle_token(
    pool: web::Data, // CockroachDB compatible pool
    encrypted: web::Json,
) -> HttpResponse {
    let encrypted_bytes = match base64::decode(&encrypted.0) {
        Ok(b) => b,
        Err(_) => return HttpResponse::bad_request().json(ErrorMessage { error: "invalid_request" }),
    };
    let decrypted = match decrypt_token_constant(&encrypted_bytes, &KEY).map_err(|_| ()) {
        Ok(d) => d,
        Err(_) => {
            // Always return the same generic error and status.
            return HttpResponse::bad_request().json(ErrorMessage { error: "invalid_token" });
        }
    };
    // Continue with normalized logic; avoid exposing DB-specific errors.
    match extract_user_and_query(&pool, &decrypted).await {
        Ok(user) => HttpResponse::Ok().json(user),
        Err(_) => HttpResponse::internal_server_error().json(ErrorMessage { error: "service_error" }),
    }
}

async fn extract_user_and_query(pool: &sqlx::PgPool, decrypted: &[u8]) -> Result {
    let user: User = sqlx::query_as("SELECT username, role FROM users WHERE id = $1")
        .bind(decrypted)
        .fetch_one(pool)
        .await?;
    Ok(user)
}