💡 GitHubで利用できる完全な動作例: compare-encrypted-pdf-and-word-documents-dotnet
古い方法は苦痛だった
2つの改訂版の供給契約書が受信トレイに届きます。どちらもパスワードで保護されており、パスワードがそれぞれ異なります。誰かが変更点を示すマークアップ済みのコピーを必要としています。使用している比較ライブラリはプレーンテキスト入力を期待しているため、パイプラインにステップが増えます:両方のファイルを一時フォルダーに復号し、プレーンテキストのコピーを比較し、最後にそれらを削除します。その一時フォルダーが、文書が機密であるために特別に存在するワークフローの最も弱いリンクになります。
同じ問題の第2のバージョンは見落としやすいです。いくつかのチームは一時フォルダーを省略し、代わりにメモリ上で復号します。これによりクリーンアップの問題は解決しますが、フォーマットの問題は残ります:復号 API はフォーマットごとに異なるため、暗号化された PDF の後に暗号化されたスプレッドシートをサポートすることは、コードの第2行ではなく第2の統合になります。
コストは主に復号呼び出しにあるわけではなく、その周辺にあります。プレーンテキストのコピーはどこかに書き込まれ、失敗パスを含むすべての終了パスでクリーンアップされ、バックアップやクラッシュダンプから除外されなければなりません。そのように生成された差分はデフォルトで保護されていないため、2つの暗号化された入力から生成された出力は、誰でも開くことができるチェーン上の唯一のファイルになります。
復号迂回の実際のコスト:暗号化された文書のプレーンテキストコピーを保持する一時ディレクトリ。すべてのエラーパスで正しくクリーンアップする必要があります。
より良い方法があります
パスワード保護された比較は、.NET 用の GroupDocs.Comparison 機能で、PDF、DOCX、XLSX、PPTX ファイルをその場で開き、比較結果を保護するパスワードを決定します。復号ステップもプレーンテキストの中間物も不要です:パスワードは LoadOptions のプロパティとして文書とともに比較に渡されます。
開始する前に必要なもの:
- .NET 8.0 SDK 以降
- GroupDocs.Comparison 26.9.0(temporary licence)
- 同じフォーマットの暗号化された文書 2 件とそれぞれのパスワード
1 行でインストール:
dotnet add package GroupDocs.Comparison
新しい方法:暗号化されたドキュメントを直接比較器に渡す
以下の例は、2 つの暗号化された PDF を比較します。ソースは 1234、ターゲットは 4321 で開き、変更点をインラインでマージした単一の結果ファイルを書き出します。意図的に異なるパスワードを使用しています。これは最初のミスが隠れる場所です。
ステップ1 - 各ドキュメントに個別の LoadOptions を設定する
Comparer は 1 つのソースと任意の数のターゲットを保持し、各文書はそれぞれの保護情報を持ちます。ソースのパスワードはコンストラクタに渡し、各ターゲットのパスワードはそれぞれの Add 呼び出しに渡します。
// 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" });
これが人を混乱させるポイントです。コンストラクタに単一の LoadOptions を渡し、ターゲットにも適用されると期待するのが最も一般的な間違いで、失敗のタイミングが遅いため、どこで問題が起きたかがすぐに分かりません。
ステップ2 - 結果を保護する方法を決める
CompareOptions.PasswordSaveOption は出力の保護方法を選択します:None、Source、Target、または User。デフォルトは None で、2 つの暗号化入力を無保護の結果に静かに変換します。
// 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);
重要ポイント:
- PasswordSaveOption:
Sourceは出力にソースのパスワードを再利用します。新しいパスワードを設定したい場合はUserとSaveOptions.Passwordを組み合わせます。 - ComparisonDisplayMode:
PdfCompareOptions内にネストされた列挙型で、SideBySideやInterleavedも提供します。WordCompareOptionsでも同名の列挙型が別の値を持つため、名前だけではコンパイルできません。必ず型名で修飾してください。
ステップ3 - 出力自体にパスワードで保護する
レビュー担当者が元のパスワードを保持すべきでない場合、PasswordSaveOption.User は SaveOptions.Password の値を使用し、入力のパスワードを再利用しません。
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);
3 つの引数を持つ Compare オーバーロードに両方のオブジェクトを渡します。単独で SaveOptions.Password を設定しても何も変わりません。列挙値が保存側のパスワード適用をトリガーします。この呼び出しの結果は 5678 で開き、1234 は拒否されます。
なぜ Comparer の周りの try/catch が間違ったパスワードを捕捉できないのか?
コンストラクタは文書を開きません。パスだけを記録し、Add も同様です。実際に文書が読み込まれるのは Compare が実行されたときで、そこで PasswordProtectedFileException(メッセージは Password is missing)がスローされます。間違ったパスワードも同様に、コンストラクション時には黙って受け入れられ、Compare 時に拒否されます。
したがって、コンストラクタではなく比較呼び出しを try/catch で保護してください。私がこの問題にたどり着くまでに、コンストラクションを try でラップし、暗号化ファイルが 3 行後に失敗するまで通過する様子を観察しました。リポジトリは各ステージを出力するので、最初の読解で順序が明らかになります。
using var comparer = new Comparer("source.pdf"); // succeeds
comparer.Add("target.pdf"); // succeeds
comparer.Compare("Result/unreachable.pdf"); // throws here
比較:前後
| 前(最初に復号) | 後(GroupDocs.Comparison) | |
|---|---|---|
| パイプライン手順 | 両方を復号、比較、一時コピーを削除 | 比較 |
| ディスク上の平文 | 2 つのコピー、すべてのエラーパスでクリーンアップ | なし |
| 結果の保護 | 別途再暗号化ステップ | 1 つの PasswordSaveOption 値 |
| フォーマット対応 | フォーマットごとの復号ツール | PDF、DOCX、XLSX、PPTX 用の単一 LoadOptions.Password |
| 必要なコード | 復号ヘルパー+比較 | 4 行 |
暗号化入力でも比較機能は変わりません。表示モード、サマリーページ、スタイル検出はプレーンテキストファイルと同様に動作します。保護は完全にロード層で処理されるためです。
このレイヤリングがフォーマット対応を安価にしています。LoadOptions.Password は単なる string プロパティで、PDF、DOCX、XLSX、PPTX のロックを同じプロパティで解除します。下の Word の例のロードコードは PDF の例と文字単位で同一です。オプションクラスだけがフォーマットごとに異なり、各フォーマットが異なるレンダリング選択肢を公開しているだけです。暗号化されたスプレッドシートのサポートを、すでに暗号化 PDF を比較できるコードに追加しても、ロードパスでは何も追加する必要がありません。
実務例:法律事務所間の契約書の赤字修正
法務チームは各改訂版を暗号化して受け取り、パスワードはやり取りごとにローテーションします。これにより、パスワードが漏洩しても履歴全体が露出しません。レビュー担当パートナーは各ラウンドで 1 つのマークアップ済み文書を必要とし、保存規則によりマークアップコピーはファイル共有上で無保護のまま置かれてはいけません。
2 つの設定で対応できます。各文書はそれぞれの LoadOptions で解除されるため、パスワードのローテーションに特別な処理は不要です。また PasswordSaveOption.User により、配布する各差分に独自のパスワードが付与されます。これにより比較は開くことができても、他の情報にはアクセスできません。
// 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);
GroupDocs.Comparisonでできることは他に何ですか?
- 保護された文書を 2 つ以上比較:暗号化されたターゲットを複数追加して、Word やプレゼンテーション形式でも比較可能です。
- ネイティブな Word の改訂版を生成:
WordCompareOptions.ComparisonDisplayMode.Revisionsは、レビュー担当者が Word 内で変更を受け入れたり拒否したりできる形で書き出します。 - 外部リソースの読み込みを制御:文書が保持するリモート参照をブロックまたはホワイトリスト化できる、別の
LoadOptionsの保護機能です。 - サマリーページを生成:
GenerateSummaryPageを使用すると、結果文書に変更概要が追加されます。
結論
復号迂回は比較そのものが問題だったわけではなく、読み取れないライブラリが問題でした。各文書に LoadOptions.Password を設定すれば、一時フォルダー、クリーンアップパス、チェーン最後の無保護差分がすべて不要になります。残るのは 3 つの決定だけです:文書ごとのパスワード、None がデフォルトの代わりに明示的な PasswordSaveOption、そして失敗が実際に起こる Compare 周りのエラーハンドリングです。
ドキュメントワークフローを自動化する準備はできましたか?