💡 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: Source reuses the source password on the output. Pick User with SaveOptions.Password to issue a new one instead.
  • ComparisonDisplayMode: nested inside PdfCompareOptions, which also offers SideBySide and Interleaved. WordCompareOptions declares 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.Revisions writes changes a reviewer accepts or rejects in Word itself.
  • Control external resource loading: block or whitelist the remote references a document carries, another LoadOptions safeguard.
  • Generate a summary page: GenerateSummaryPage adds 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?

Additional Resources