trailofbits/algo · error

Invalid base64 private key format: {e}

Error message

Invalid base64 private key format: {e}

What it means

The provided base64 private key failed strict decoding (base64.b64decode with validate=True): it contains characters outside the base64 alphabet or its length is not a multiple of 4. The binascii.Error detail is embedded in the message.

Source

Thrown at library/x25519_pubkey.py:88

                # Stripping would corrupt the key and cause "got 31 bytes" errors
                if len(data) != 32:
                    module.fail_json(
                        msg=f"Private key file must be either base64 or exactly 32 raw bytes, got {len(data)} bytes"
                    )
                priv_b64 = base64.b64encode(data).decode()
        except OSError as e:
            module.fail_json(msg=f"Failed to read private key file: {e}")
    else:
        priv_b64 = module.params["private_key_b64"]

    # Validate input parameters
    if not priv_b64:
        module.fail_json(msg="No private key provided")

    try:
        priv_raw = base64.b64decode(priv_b64, validate=True)
    except Exception as e:
        module.fail_json(msg=f"Invalid base64 private key format: {e}")

    if len(priv_raw) != 32:
        module.fail_json(msg=f"Private key must decode to exactly 32 bytes, got {len(priv_raw)}")

    try:
        priv_key = x25519.X25519PrivateKey.from_private_bytes(priv_raw)
        pub_key = priv_key.public_key()
        pub_raw = pub_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
        pub_b64 = base64.b64encode(pub_raw).decode()
        result["public_key"] = pub_b64

        if module.params["public_key_path"]:
            pub_path = module.params["public_key_path"]
            existing = None

            try:
                with open(pub_path) as f:
                    existing = f.read().strip()

View on GitHub (pinned to 20e22a8715)

Solutions

  1. Strip whitespace: private_key_b64 | trim in Jinja, or .strip() in Python
  2. Convert base64url to standard base64 (replace - with +, _ with /) and add padding
  3. Re-copy the key carefully; verify it is 44 chars ending with '=' for a 32-byte key
  4. Regenerate the key pair if provenance is unknown

Example fix

# before
private_key_b64: "{{ lookup('file', path) }}"
# after
private_key_b64: "{{ lookup('file', path) | trim }}"
Defensive patterns

Strategy: validation

Validate before calling

import base64
key = key_str.strip().replace('-', '+').replace('_', '/')
key += '=' * (-len(key) % 4)
base64.b64decode(key, validate=True)  # raises if still invalid

Type guard

def is_base64_key(s: str) -> bool:
    import base64
    try:
        return len(base64.b64decode(s.strip(), validate=True)) == 32
    except Exception:
        return False

Prevention

When it happens

Trigger: Key string with whitespace/newlines, URL-safe base64 (-/_ instead of +/), hex text, or a truncated/copy-paste-corrupted key passed via private_key_b64.

Common situations: Reading a key file without stripping the trailing newline, copying keys through a medium that mangled + into space, or using base64url output from another tool.

Related errors


AI-assisted analysis of trailofbits/algo@20e22a8715 (2026-08-28). Data as JSON: /api/errors/f5c9b588f0445ad0. Report an issue: GitHub.