In this article, we'll explore how to implement a pure client-side PDF unlocking tool that runs entirely in the browser. This tool removes password protection and encryption from PDF files, making it perfect for recovering access to your own documents.
Why Browser-Based PDF Unlocking?
Traditional PDF unlocking typically requires:
- Uploading sensitive documents to a server
- Trusting third-party services with password-protected files
- Potential security risks and privacy concerns
Browser-based processing solves all these issues:
- ✅ Files never leave your computer - complete privacy
- ✅ No password transmission over the network
- ✅ Instant processing with no upload delays
- ✅ Zero server costs
- ✅ No risk of documents being stored or logged
The Challenge: PDF Encryption
PDF files can be protected with:
- User Password: Required to open the document
- Owner Password: Controls permissions (printing, copying, etc.)
- Encryption: Content is encrypted using various algorithms (RC4, AES)
Unlocking a PDF requires:
- Decrypting the content with the correct password
- Removing encryption dictionaries
- Creating an unencrypted copy
Architecture Overview
Key Data Structures
WorkerFunctions Interface
// hooks/useqpdf.ts
interface WorkerFunctions {
init: () => Promise<void>;
unlock: (file: File) => Promise<ArrayBuffer | null>;
// ... other operations
}
File Processing Result
// Output is a decrypted ArrayBuffer
// No specific data structure - raw PDF bytes
Implementation Deep Dive
1. User Interface Component
The unlock component provides a simple drag-and-drop interface:
// app/[locale]/_components/qpdf/unlock.tsx
export const Unlock = () => {
const [files, setFiles] = useState<File[]>([]);
const { unlock } = useQpdf();
const tl = useTranslations("Unlock");
const unlockInMain = async () => {
for (const file of files) {
// Call unlock function for each file
const outputFile = await unlock(file);
const fullName = file.name;
// Extract filename without extension
const lastDotIndex = fullName.lastIndexOf(".");
const fileNameWithoutExt =
lastDotIndex !== -1 ? fullName.substring(0, lastDotIndex) : fullName;
if (outputFile) {
autoDownloadBlob(
new Blob([outputFile]),
fileNameWithoutExt + "_unlock.pdf",
);
}
}
};
const onPdfFiles = (files: File[]) => {
setFiles(files);
};
return (
<PdfPage
process={unlockInMain}
onFiles={onPdfFiles}
title={tl("title")}
desp={tl("desp")}
>
<div>{tl("desp")}</div>
</PdfPage>
);
};
Key features:
- Simple UI - just upload and unlock
- Batch processing support (multiple files)
- Automatic filename generation (
_unlock.pdfsuffix) - No password input required (for owner password removal)
2. Worker Management with Comlink
The useQpdf hook manages the Web Worker:
// hooks/useqpdf.ts
export const useQpdf = () => {
const workerRef = useRef<Comlink.Remote<WorkerFunctions>>(null);
useEffect(() => {
async function initWorker() {
if (workerRef.current) return;
const worker = new PdfWorker();
worker.onerror = (error) => {
console.error("Worker error:", error);
};
workerRef.current = Comlink.wrap<WorkerFunctions>(worker);
await workerRef.current.init();
return () => worker.terminate();
}
initWorker().catch(() => { return; });
}, []);
const unlock = async (file: File): Promise<ArrayBuffer | null> => {
console.log("Calling unlock in main thread", workerRef.current);
if (!workerRef.current) return null;
const r = await workerRef.current.unlock(file);
return r;
};
return { unlock };
};
3. QPDF Unlock Implementation in Web Worker
The actual unlocking happens in the Web Worker using QPDF:
// hooks/pdf.worker.js
async unlock(file) {
console.log("Executing unlock in worker");
if (!qpdf) {
console.log("qpdf not loaded yet");
return null;
}
if (!file) {
console.log("file is required");
return null;
}
// Convert File to ArrayBuffer
const arrayBuffer = await file.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
// Write input to virtual filesystem
qpdf.FS.writeFile("/input.pdf", uint8Array);
// Execute QPDF decrypt command
qpdf.callMain(["/input.pdf", "--decrypt", "/output.pdf"]);
// Read decrypted output from virtual filesystem
const outputFile = qpdf.FS.readFile("/output.pdf");
return outputFile;
}
QPDF decrypt command:
# Decrypt a PDF and remove password protection
qpdf input.pdf --decrypt output.pdf
# With password (if user password is known)
qpdf --password=yourpassword input.pdf --decrypt output.pdf
# Force decryption (for owner password removal)
qpdf input.pdf --decrypt output.pdf
Command explanation:
-
--decrypt: Remove encryption and password protection - Input file:
/input.pdf - Output file:
/output.pdf - QPDF automatically handles various encryption types
4. QPDF WASM Initialization
The QPDF WASM module is initialized with Emscripten:
// lib/qpdfwasm.js
import createModule from "@neslinesli93/qpdf-wasm";
const f = async () => {
const qpdf = await createModule({
locateFile: () => "/qpdf.wasm",
noInitialRun: true, // Don't run main() immediately
});
return qpdf;
};
export default f;
Complete Processing Flow
How QPDF Handles Decryption
QPDF performs decryption by:
1. Detecting Encryption
% Encrypted PDF trailer
<<
/Size 95
/Root 93 0 R
/Encrypt 94 0 R % Encryption dictionary reference
/ID [<...> <...>]
>>
2. Removing the Encrypt Dictionary
QPDF removes the /Encrypt entry from the trailer dictionary, which tells PDF readers that the file is no longer encrypted.
3. Decrypting Content Streams
All content streams in the PDF are decrypted using the appropriate algorithm:
- RC4: Older encryption (40-bit, 128-bit)
- AES: Modern encryption (128-bit, 256-bit)
4. Handling Passwords
QPDF can handle two types of passwords:
User Password: Required to open the file
- If you know the user password, provide it with
--password - If you don't know it, QPDF may still be able to remove encryption in some cases
Owner Password: Controls permissions
- QPDF can often remove owner password restrictions without knowing the password
- This is because owner password protection is designed for permissions, not security
Key Technical Decisions
1. Why QPDF for Unlocking?
QPDF is ideal for unlocking because:
- Supports all PDF encryption standards
- Handles both user and owner passwords
- Preserves document structure and content
- Fast and reliable decryption
- Can remove restrictions without passwords (for owner passwords)
2. Why No Password Input?
The current implementation doesn't require password input because:
- It primarily targets owner password removal (permissions)
- QPDF can often remove owner passwords automatically
- For user password-protected files, the browser's PDF viewer may prompt
For user password-protected files, you can modify the code:
// With password parameter
async unlock(file, password) {
qpdf.FS.writeFile("/input.pdf", uint8Array);
// Add password parameter
qpdf.callMain([
"--password=" + password,
"/input.pdf",
"--decrypt",
"/output.pdf"
]);
return qpdf.FS.readFile("/output.pdf");
}
3. Batch Processing
The component supports multiple files:
for (const file of files) {
const outputFile = await unlock(file);
if (outputFile) {
autoDownloadBlob(
new Blob([outputFile]),
fileNameWithoutExt + "_unlock.pdf",
);
}
}
Each file is processed sequentially and downloaded individually.
Security and Legal Considerations
Important Notice
This tool is designed for legitimate use cases only:
- Unlocking PDFs you own but forgot the password
- Removing restrictions from documents you have rights to modify
- Recovering access to your own files
Do not use this tool to:
- Bypass security on documents you don't own
- Remove protection from copyrighted material without permission
- Circumvent DRM or access controls
Technical Limitations
- Cannot unlock PDFs with unknown user passwords (strong encryption)
- Some PDFs use certificate-based encryption (not supported)
- Digital signature protection may prevent unlocking
Benefits of This Architecture
- Privacy First: Files and passwords never leave the browser
- Fast Processing: QPDF is highly optimized for decryption
- No Server Required: Zero backend infrastructure
- Batch Support: Process multiple files at once
- Preserves Content: Document structure and quality maintained
- Responsive UI: Web Workers prevent blocking
Try It Yourself
Want to unlock your password-protected PDFs without uploading them to a server? Try our free browser-based tool:
All processing happens locally in your browser - your files and passwords never leave your computer!
Conclusion
Building a browser-based PDF unlocking tool demonstrates how QPDF compiled to WebAssembly can handle PDF decryption tasks efficiently and securely. By processing entirely in the browser, we ensure complete privacy for sensitive documents.
This approach is ideal for:
- Recovering access to your own password-protected files
- Removing printing/copying restrictions from documents you own
- Batch processing multiple locked PDFs
- Maintaining document confidentiality
The simple interface makes it easy to unlock PDFs with just a few clicks, while the underlying QPDF engine handles the complex decryption algorithms reliably and securely.


Top comments (0)