BerriAI/litellm · critical · TypeError

The provided private key is not an RSA key, which is require

Error message

The provided private key is not an RSA key, which is required for OCI signing.

What it means

load_private_key_from_str loads the PEM private key and enforces that it is an RSA key, because OCI request signing uses RSA PKCS1v15 + SHA256. An EC/Ed25519 key parses fine but cannot produce a valid OCI signature, so it is rejected immediately with TypeError instead of producing cryptic 401s.

Source

Thrown at litellm/llms/oci/common_utils.py:123

def build_signature_string(method: str, path: str, headers: dict, signed_headers: list) -> str:
    lines: Final = []
    for header in signed_headers:
        if header == "(request-target)":
            value = f"{method.lower()} {path}"
        else:
            value = headers[header]
        lines.append(f"{header}: {value}")
    return "\n".join(lines)


def load_private_key_from_str(key_str: str) -> Any:
    _require_cryptography()
    key: Final = serialization.load_pem_private_key(
        key_str.encode("utf-8"),
        password=None,
    )
    if not isinstance(key, rsa.RSAPrivateKey):
        raise TypeError("The provided private key is not an RSA key, which is required for OCI signing.")
    return key


def load_private_key_from_file(file_path: str) -> Any:
    """Loads a private key from a file path."""
    try:
        with open(file_path, "r", encoding="utf-8") as f:
            key_str: Final = f.read().strip()
    except FileNotFoundError:
        raise FileNotFoundError(f"Private key file not found: {file_path}")
    except OSError as e:
        raise OSError(f"Failed to read private key file '{file_path}': {e}") from e

    if not key_str:
        raise ValueError(f"Private key file is empty: {file_path}")

    return load_private_key_from_str(key_str)

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Generate an RSA key: openssl genrsa -out oci_key.pem 2048 (or ssh-keygen -t RSA -b 2048 -m PEM).
  2. Upload the new public key in the OCI Console (Identity > Users > API Keys) and copy the new fingerprint into OCI_FINGERPRINT.
  3. Point oci_key_file / OCI_KEY_FILE at the RSA PEM file.
  4. Verify: openssl rsa -in oci_key.pem -check -noout succeeds only for RSA keys.

Example fix

# before (ed25519/EC key → TypeError)
# key generated with: ssh-keygen -t ed25519

# after
# openssl genrsa -out ~/.oci/oci_key.pem 2048
# upload public key in OCI Console, update fingerprint
litellm.completion(model="oci/...", messages=m)  # signs successfully
Defensive patterns

Strategy: validation

Validate before calling

from cryptography.hazmat.primitives import serialization
key = serialization.load_pem_private_key(pem.encode(), password=None)
from cryptography.hazmat.primitives.asymmetric import rsa
assert isinstance(key, rsa.RSAPrivateKey), "OCI requires an RSA private key"

Type guard

from cryptography.hazmat.primitives.asymmetric import rsa

def is_rsa_key(key) -> bool:
    return isinstance(key, rsa.RSAPrivateKey)

Try / catch

try:
    litellm.completion(model="oci/...", messages=m)
except TypeError as e:
    if "not an RSA key" in str(e):
        raise ConfigError("Regenerate key: openssl genrsa -out oci_key.pem 2048 and re-upload") from e
    raise

Prevention

When it happens

Trigger: The OCI_USER's API key was generated as ECDSA or Ed25519 instead of RSA-2048+, or the wrong key file (e.g. an SSH ed25519 key) was pointed at by OCI_KEY_FILE / oci_key_file.

Common situations: Developers reusing ~/.ssh/id_ed25519; OpenSSL defaults drifting to EC keys (openssl genpkey -algorithm EC); keys generated for a different cloud provider; copying an OCI config file whose key_file path points at an EC key generated later.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/e5b89946f0b4de07. Report an issue: GitHub.