# Handling Password-Protected Encrypted PDFs with pdf-inspector

> Learn how pdf-inspector effortlessly handles password-protected encrypted PDFs. Discover how to provide passwords and automatically decrypt files for seamless inspection.

- Repository: [Firecrawl/pdf-inspector](https://github.com/firecrawl/pdf-inspector)
- Tags: how-to-guide
- Published: 2026-08-10

---

**pdf-inspector handles encrypted PDFs by accepting a password through the `PdfOptions` builder, automatically attempting decryption with `lopdf`, and falling back to an empty password for owner-only protection.**

The `firecrawl/pdf-inspector` Rust library provides built-in support for opening and processing password-protected PDFs without requiring external preprocessing. All decryption logic is contained in the core library at [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs), making encrypted document handling transparent to downstream operations like type detection and Markdown extraction.

## How Password Protection Works in pdf-inspector

The library implements a two-tiered approach to password handling: user-provided passwords take precedence, with automatic fallback to empty passwords for common owner-only encryption schemes.

### The PdfOptions Builder Pattern

Password entry begins with the **`PdfOptions`** struct, which exposes an optional `password` field. According to the source code at [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) lines 185-188, this field carries a clear doc comment explaining the fallback behavior:

```rust
pub struct PdfOptions {
    …
    /// Password for decrypting an encrypted PDF. `None` falls back to the
    /// empty password (owner‑only encryption).
    pub password: Option<String>,
}

```

When `password` is `None`, the library automatically attempts decryption with an empty string—sufficient for PDFs with owner passwords but no user passwords.

### Password-Aware Document Loading

All high-level API entry points in pdf-inspector—including **`process_pdf`**, **`process_pdf_mem`**, **`detect_pdf`**, and their `_with_options` variants—route through dedicated password-aware loader functions. These internal helpers are defined at [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) lines 75-78:

```rust
pub(crate) fn load_document_from_path_with_password<P: AsRef<Path>>(
    path: P,
    password: Option<&str>,
) -> Result<(Document, u32), PdfError> { … }

```

A corresponding `load_document_from_mem_with_password` handles in-memory byte buffers. Both functions forward the optional password to the low-level decryption routine, ensuring consistent behavior across file and memory-based workflows.

## The Decryption Routine: Automatic Fallback Handling

The actual decryption logic resides in **`decrypt_document_bytes`** at [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) lines 40-48. This function implements a robust retry mechanism using the underlying `lopdf` crate:

```rust
fn decrypt_document_bytes(buf: &[u8], password: Option<&str>) -> Result<Document, lopdf::Error> {
    let pw = password.unwrap_or("");
    match Document::load_mem_with_options(buf, lopdf::LoadOptions::with_password(pw)) {
        Ok(doc) => Ok(doc),
        Err(inner) if !pw.is_empty() => {
            Document::load_mem_with_options(buf, lopdf::LoadOptions::with_password(""))
                .map_err(|_| inner)
        }
        Err(inner) => Err(inner),
    }
}

```

This implementation provides two key reliability features:

1. **Primary attempt** – Tries the user-supplied password (or empty string if none provided)
2. **Automatic fallback** – If the primary attempt fails with a non-empty password, retries with an empty password before returning the original error

The fallback covers PDFs encrypted with owner passwords where users legitimately lack the user password but the document permits empty-password access.

## Practical Code Examples

### Processing a Password-Protected File from Disk

```rust
use pdf_inspector::{process_pdf_with_options, PdfOptions};

fn main() -> Result<(), pdf_inspector::PdfError> {
    // Configure options with the decryption password
    let opts = PdfOptions::new()
        .password("my-secret-pw");

    // Process the encrypted PDF transparently
    let result = process_pdf_with_options("protected.pdf", opts)?;
    println!("PDF type: {:?}, pages: {}", result.pdf_type, result.page_count);
    
    if let Some(md) = result.markdown {
        println!("{}", md);
    }
    Ok(())
}

```

### Processing In-Memory PDF Data

```rust
use pdf_inspector::PdfOptions;

fn process_downloaded_pdf(data: &[u8]) -> Result<(), pdf_inspector::PdfError> {
    let opts = PdfOptions::new().password("my-secret-pw");
    
    let result = pdf_inspector::process_pdf_mem_with_options(data, opts)?;
    // Same result structure as file-based processing
    println!("Detected type: {:?}", result.pdf_type);
    Ok(())
}

```

Both examples demonstrate that **password handling does not alter the API contract**—the same `PdfResult` type is returned regardless of encryption status.

## Security Considerations

### Password Redaction in Debug Output

The `PdfOptions` struct implements a custom **`Debug`** trait that explicitly redacts the password field. This prevents accidental credential exposure in logs, backtraces, or diagnostic output. Your passwords remain confidential even when debugging or serializing configuration objects.

### No Password Persistence

pdf-inspector does not cache, store, or transmit passwords beyond the immediate decryption operation. The password exists only in memory during `Document` loading and is dropped immediately after.

## Integration Points in the Source Code

| Component | File Path | Role in Password Handling |
|-----------|-----------|---------------------------|
| Options definition | [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L185-L188) | `PdfOptions` struct with `password` field |
| Password-aware loaders | [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L75-L78) | `load_document_from_path_with_password`, `load_document_from_mem_with_password` |
| Decryption implementation | [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L40-L48) | `decrypt_document_bytes` with `lopdf` integration and fallback logic |
| Extractor integration | [`src/extractor/mod.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/extractor/mod.rs) | Calls password-aware loaders before detection/extraction |
| Type detection | [`src/detector.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/detector.rs) | Operates on decrypted `Document` objects transparently |

## Summary

- **Password entry**: Use `PdfOptions::new().password("your-pw")` before any processing call
- **Automatic fallback**: pdf-inspector retries with empty passwords when initial decryption fails
- **Universal API**: File-based (`process_pdf_with_options`) and memory-based (`process_pdf_mem_with_options`) functions share identical password handling
- **Security by default**: Passwords are redacted from `Debug` output and never persisted

## Frequently Asked Questions

### What happens if I provide the wrong password?

pdf-inspector returns the original `lopdf::Error` after attempting both the provided password and an empty password fallback. The error indicates decryption failure—handle it in your application to prompt users for correct credentials.

### Can pdf-inspector crack PDF passwords?

No. The library only attempts decryption with passwords you provide, plus a single empty-password fallback for owner-only protection. It does not implement brute-force, dictionary attacks, or cryptographic bypasses.

### Does password protection affect Markdown extraction quality?

No. Once decrypted, the `Document` object passed to the extractor and detector layers is identical to a never-encrypted PDF. All downstream processing—type detection, content extraction, and Markdown generation—operates identically.

### How do I handle PDFs where I don't know if they're encrypted?

Simply omit the `.password()` call. pdf-inspector attempts empty-password decryption automatically, which succeeds for unencrypted PDFs and owner-only encrypted PDFs. Only supply a password when you know user-password encryption is present.