Handling Password-Protected Encrypted PDFs with pdf-inspector
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, 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 lines 185-188, this field carries a clear doc comment explaining the fallback behavior:
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 lines 75-78:
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 lines 40-48. This function implements a robust retry mechanism using the underlying lopdf crate:
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:
- Primary attempt – Tries the user-supplied password (or empty string if none provided)
- 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
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
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 |
PdfOptions struct with password field |
| Password-aware loaders | src/lib.rs |
load_document_from_path_with_password, load_document_from_mem_with_password |
| Decryption implementation | src/lib.rs |
decrypt_document_bytes with lopdf integration and fallback logic |
| Extractor integration | src/extractor/mod.rs |
Calls password-aware loaders before detection/extraction |
| Type detection | 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
Debugoutput 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →