# How pdf-inspector Handles Password-Protected Encrypted PDFs in Rust

> Learn how pdf-inspector in Rust handles password-protected encrypted PDFs. Decrypt PDFs with provided or default passwords using its PdfOptions API.

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

---

**`pdf-inspector` processes encrypted PDFs by detecting encryption at load time, accepting an optional password through its `PdfOptions` API, and attempting decryption with the provided password or falling back to an empty password for owner-only protection.**

Encrypted PDF handling is a critical capability for any document processing library. The `firecrawl/pdf-inspector` repository implements a robust, multi-layered approach to password-protected PDFs that integrates seamlessly into both its Rust API and CLI interface. This article explains exactly how the library detects, decrypts, and processes encrypted documents based on the actual source code implementation.

## PDF Options: Configuring Password Protection

The entry point for password handling begins with **`PdfOptions`**, the configuration builder exposed in [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs). This struct contains a `password: Option<String>` field that stores the optional decryption key ([L254-L260](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L254-L260)).

```rust
use pdf_inspector::PdfOptions;

// Create options with a password
let opts = PdfOptions::new().password("secret123");

```

Security-conscious design is evident in the builder's implementation. The `password()` method deliberately **redacts the password from `Debug` output** to prevent accidental logging of sensitive credentials ([L190-L199](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L190-L199)). This ensures that diagnostic logs never expose the actual password value.

## Document Loading with Password Support

All PDF loading operations in `pdf-inspector` route through password-aware functions. The two primary entry points are:

- **`load_document_from_path_with_password`** – for file-based loading
- **`load_document_from_mem_with_password`** – for in-memory PDF data

Both functions accept `Option<&str>` for the password parameter ([L3475-L3490](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L3475-L3490)). These functions delegate to `load_document_bytes`, which orchestrates the actual decryption logic.

## Detecting Encryption at Runtime

The `load_document_bytes` function implements **automatic encryption detection**. It first attempts to load the PDF normally using `lopdf::Document::load_mem`. If either of these conditions occurs, it triggers the decryption fallback ([L3528-L3532](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L3528-L3532)):

- The loaded document returns `true` from `is_encrypted()`
- The loader returns an encryption-related `lopdf::Error`

This approach avoids unnecessary decryption attempts on unencrypted documents while ensuring encrypted files are properly handled.

## The Decryption Strategy

When encryption is detected, `pdf-inspector` invokes **`decrypt_document_bytes`** with a carefully designed retry strategy ([L3538-L3542](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L3538-L3542)):

```rust
// Pseudocode representing the actual implementation
let password_to_try = provided_password.unwrap_or("");
let result = try_decrypt_with_password(password_to_try);

if result.is_err() && provided_password.is_some() {
    // Retry with empty password for owner-only encryption
    try_decrypt_with_password("")
}

```

The function uses `lopdf::LoadOptions::with_password` for the actual decryption. Key behaviors include:

- **User password provided** – passed directly to `lopdf`
- **No password provided** – defaults to empty string for owner-only encryption
- **Retry logic** – if the provided password fails, attempts empty password as fallback ([L3545-L3547](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L3545-L3547))

## Public API Integration

The high-level helper **`process_pdf_with_options`** bridges the configuration to execution. It forwards the password stored in `PdfOptions` directly to the internal loader ([L292-L298](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L292-L298)):

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

// Process encrypted PDF with password
let opts = PdfOptions::new().password("secret123");
let result = process_pdf_with_options("encrypted.pdf", opts)?;

// Attempt without password — returns Err for encrypted files
let fail = process_pdf_with_options("encrypted.pdf", PdfOptions::new());

```

## CLI Password Support

The `pdf2md` binary exposes password functionality through the **`--password`** flag. Implementation in [`src/bin/pdf2md.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/pdf2md.rs) ([L242-L287](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/pdf2md.rs#L242-L287)) parses this argument and forwards it through `extract_text_with_positions_pages_with_password`, ensuring parity between programmatic and command-line usage:

```bash

# Convert encrypted PDF to Markdown

pdf2md --password secret123 encrypted.pdf -o output.md

# Without password — clear error for encrypted files

pdf2md encrypted.pdf  # fails if PDF is encrypted

```

## Testing Encrypted PDF Handling

The test suite in [`tests/integration_tests.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/tests/integration_tests.rs) validates three critical scenarios ([L3899-L3918](https://github.com/firecrawl/pdf-inspector/blob/main/tests/integration_tests.rs#L3899-L3918)):

1. **No password supplied** – operation fails with appropriate error
2. **Wrong password provided** – decryption fails, error propagated
3. **Correct password provided** – successful document processing

These integration tests ensure that `pdf-inspector` never silently processes encrypted content and provides clear feedback when authentication fails.

## Summary

- **`PdfOptions`** provides the configuration interface with automatic password redaction in debug output
- **Automatic detection** identifies encrypted PDFs during initial load attempts
- **Smart decryption** tries provided passwords first, then falls back to empty password for owner-only protection
- **Unified API** exposes password support across library methods and CLI tools
- **Comprehensive testing** verifies failure modes and success paths for encrypted documents

## Frequently Asked Questions

### What happens if I don't provide a password for an encrypted PDF?

`pdf-inspector` attempts decryption with an empty string, which succeeds only for owner-only encrypted PDFs. For user-password-protected documents, the operation returns an error that clearly indicates decryption failure. The library never processes encrypted content without authentication.

### How does pdf-inspector protect passwords from appearing in logs?

The `PdfOptions` builder implements a custom `Debug` trait that redacts the password field, displaying `"[REDACTED]"` instead of the actual value ([L190-L199](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs#L190-L199)). This prevents accidental credential exposure in stack traces and log files.

### Can pdf-inspector crack or bypass PDF encryption?

No. The library relies entirely on `lopdf` for decryption and only attempts the specific password provided by the caller (plus an empty string fallback). There is no brute-force capability, password recovery, or encryption bypass implemented in the source code.

### What encryption standards does pdf-inspector support?

Encryption support depends on the underlying `lopdf` crate. `pdf-inspector` passes passwords through to `lopdf::LoadOptions::with_password`, which handles standard PDF encryption including RC4 and AES-based methods defined in the PDF specification.