💡 Full working example available on GitHub: compare-encrypted-pdf-and-word-documents-dotnet
The Old Way Was Painful
Two revisions of a supply agreement land in your inbox. Both are password-protected, each with a different password, and someone needs a marked-up copy showing what changed. The comparison library you have expects plaintext input, so the pipeline grows a step: decrypt both files to a temp folder, compare the plaintext copies, then remember to delete them. That temp folder is now the weakest link in a workflow that exists specifically because the documents are sensitive.
There is a second version of the same problem that is easier to miss. Some teams skip the temp folder and decrypt into memory instead, which solves the cleanup question but not the format question: the decryption API differs per format, so supporting encrypted spreadsheets after encrypted PDFs means a second integration rather than a second line of code.
The cost is not mainly in the decrypt call - it is in everything around it. Plaintext copies have to be written somewhere, cleaned up on every exit path including the failure ones, and kept out of backups and crash dumps. A diff produced that way also arrives unprotected by default, so the output of two encrypted inputs becomes the one file in the chain that anyone can open.
The real cost of the decryption detour: a temp directory holding plaintext copies of documents that were encrypted for a reason, with cleanup that has to be correct on every error path.
There’s a Better Way
Password-protected comparison is a GroupDocs.Comparison capability for .NET
that opens encrypted PDF, DOCX, XLSX and PPTX files in place and decides what
password protects the comparison result. No decryption step, no plaintext
intermediates: the password travels with the document into the comparison
itself, as a property on LoadOptions.
Before we start, you’ll need:
- .NET 8.0 SDK or later
- GroupDocs.Comparison 26.9.0 (temporary licence)
- Two encrypted documents of the same format, and their passwords
Install with one command:
dotnet add package GroupDocs.Comparison
The New Way: Encrypted Documents Straight Into the Comparer
The example below compares two encrypted PDFs - the source opens with 1234,
the target with 4321 - and writes a single result file with the changes
merged inline. Deliberately different passwords, because that is where the
first mistake hides.
Step 1 - Give every document its own LoadOptions
A Comparer holds one source and any number of targets, and each document
carries its own protection. The source’s password goes to the constructor; each
target’s password goes to its own Add call.
// One LoadOptions per document - the constructor's options unlock the
// source only, and never reach the targets.
using var comparer = new Comparer("source.pdf",
new LoadOptions { Password = "1234" });
comparer.Add("target.pdf", new LoadOptions { Password = "4321" });
This is the detail that catches people out. Passing a single LoadOptions to
the constructor and expecting it to cover the targets is the most common way
this goes wrong, and because of how the failure is timed, it does not announce
itself where you would look for it.
Step 2 - Decide what protects the result
CompareOptions.PasswordSaveOption chooses the output’s protection: None,
Source, Target, or User. The default is None, which quietly turns two
encrypted inputs into one unprotected result.
// Inline markup, and the result reuses the source document's password.
var options = new PdfCompareOptions
{
DisplayMode = PdfCompareOptions.ComparisonDisplayMode.Inline,
PasswordSaveOption = PasswordSaveOption.Source
};
comparer.Compare("Result/1-pdf-inline.pdf", options);
Key points:
- PasswordSaveOption:
Sourcereuses the source password on the output. PickUserwithSaveOptions.Passwordto issue a new one instead. - ComparisonDisplayMode: nested inside
PdfCompareOptions, which also offersSideBySideandInterleaved.WordCompareOptionsdeclares its own enum of the same name with different values, so the bare name will not compile - qualify it.
Step 3 - Protect the output with a password of its own
When the diff goes to reviewers who should not hold either original password,
PasswordSaveOption.User takes the value from SaveOptions.Password instead
of reusing an input’s.
var compareOptions = new PdfCompareOptions
{
DisplayMode = PdfCompareOptions.ComparisonDisplayMode.Inline,
PasswordSaveOption = PasswordSaveOption.User
};
var saveOptions = new SaveOptions { Password = "5678" };
comparer.Compare("Result/4-own-password.pdf", saveOptions, compareOptions);
Both objects go to the three-argument Compare overload. Setting
SaveOptions.Password on its own changes nothing - the enum value is what
activates the save-side password. The result from this call opens with 5678
and rejects 1234.
Why doesn’t my try/catch around the Comparer catch a bad password?
Because the constructor never opens the document. It records the path, and so
does Add. Both documents are read when Compare runs, and that is where
PasswordProtectedFileException with the message Password is missing is
thrown. A wrong password behaves identically: it is accepted in silence at
construction time, and then rejected later at Compare.
So guard the comparison call, not the constructor. I found this the slow way,
wrapping construction in a try and watching an encrypted file sail straight
through it before failing three lines later. The repository prints each stage,
which makes the ordering obvious on a first read:
using var comparer = new Comparer("source.pdf"); // succeeds
comparer.Add("target.pdf"); // succeeds
comparer.Compare("Result/unreachable.pdf"); // throws here
Side-by-Side: Before vs. After
| Before (decrypt first) | After (GroupDocs.Comparison) | |
|---|---|---|
| Pipeline steps | Decrypt both, compare, delete temp copies | Compare |
| Plaintext on disk | Two copies, cleanup on every error path | None |
| Result protection | A separate re-encryption step | One PasswordSaveOption value |
| Format coverage | Per-format decryption tooling | One LoadOptions.Password for PDF, DOCX, XLSX, PPTX |
| Code required | Decrypt helper plus comparison | 4 lines |
The comparison features do not change on encrypted input. Display modes, summary pages and style detection behave exactly as they do for plaintext files, because protection is handled entirely in the loading layer.
That layering is what makes the format coverage cheap. LoadOptions.Password
is a plain string property, and the same property unlocks PDF, DOCX, XLSX and
PPTX - the loading code in the Word example further down is character-for-character
what the PDF examples use. Only the options class changes, and only because
each format exposes different rendering choices. Adding encrypted spreadsheet
support to code that already compares encrypted PDFs costs nothing in the
loading path.
Real-World Example: Contract Redlining Between Law Firms
A legal team receives each revision of an agreement encrypted, with the password rotated per exchange so a leaked password does not expose the whole history. The reviewing partner needs one marked-up document per round, and under retention rules the marked-up copy may not sit unprotected on a file share.
Two settings cover it. Each document is unlocked by its own LoadOptions, so
rotating passwords need no special handling, and PasswordSaveOption.User
gives each distributed diff a password of its own - one that unlocks the
comparison and nothing else.
// Word revisions, so the reviewing partner can accept or reject each edit.
var options = new WordCompareOptions
{
DisplayMode = WordCompareOptions.ComparisonDisplayMode.Revisions,
PasswordSaveOption = PasswordSaveOption.Source
};
using var comparer = new Comparer("round3.docx",
new LoadOptions { Password = "1234" });
comparer.Add("round4.docx", new LoadOptions { Password = "4321" });
comparer.Compare("Result/redline.docx", options);
What Else Can You Do with GroupDocs.Comparison?
- Compare more than two protected documents: add several encrypted targets to one comparison, for Word and presentation formats.
- Produce native Word revisions:
WordCompareOptions.ComparisonDisplayMode.Revisionswrites changes a reviewer accepts or rejects in Word itself. - Control external resource loading: block or whitelist the remote
references a document carries, another
LoadOptionssafeguard. - Generate a summary page:
GenerateSummaryPageadds a change overview to the result document.
Conclusion
The decryption detour was never about the comparison - it was about a library
that could not read what you had. Setting LoadOptions.Password per document
removes the temp folder, the cleanup paths, and the unprotected diff at the end
of the chain. Three decisions are all that is left: a password per document,
an explicit PasswordSaveOption rather than the None default, and error
handling around Compare where the failure actually arrives.
Ready to automate your document workflow?
- Try the free API trial
- Explore loading password-protected documents
- Read the full comparison guide for protected documents
- Check out the sample project on GitHub