💡 Pełny działający przykład dostępny na GitHubie:
compare-encrypted-pdf-and-word-documents-dotnet
Stara metoda była bolesna
Dwie wersje umowy dostawczej trafiają do Twojej skrzynki. Obie są zabezpieczone hasłem, każde innym hasłem, i ktoś potrzebuje wersji oznaczonej, pokazującej, co się zmieniło. Biblioteka porównująca, którą masz, oczekuje wejścia w postaci czystego tekstu, więc w potoku pojawia się dodatkowy krok: odszyfrowanie obu plików do folderu tymczasowego, porównanie czystych kopii, a potem pamiętanie o ich usunięciu. Ten folder tymczasowy staje się najsłabszym ogniwem w przepływie pracy, który istnieje właśnie dlatego, że dokumenty są wrażliwe.
Istnieje druga wersja tego samego problemu, którą łatwiej przeoczyć. Niektóre zespoły pomijają folder tymczasowy i odszyfrowują w pamięci, co rozwiązuje problem sprzątania, ale nie problem formatu: API odszyfrowywania różni się w zależności od formatu, więc wsparcie zaszyfrowanych arkuszy po zaszyfrowanych plikach PDF oznacza drugą integrację, a nie jedną dodatkową linię kodu.
Koszt nie leży głównie w wywołaniu odszyfrowania – leży we wszystkim, co go otacza. Kopie w czystym tekście muszą być zapisane gdzieś, wyczyszczone przy każdej ścieżce wyjścia, w tym przy błędach, i trzymane z dala od kopii zapasowych oraz zrzutów pamięci. Różnica wygenerowana w ten sposób również domyślnie nie jest chroniona, więc wynik dwóch zaszyfrowanych wejść staje się jedynym plikiem w łańcuchu, który każdy może otworzyć.
Rzeczywisty koszt objazdu odszyfrowania: katalog tymczasowy zawierający kopie w czystym tekście dokumentów, które zostały zaszyfrowane z jakiegoś powodu, z czyszczeniem, które musi być poprawne przy każdej ścieżce błędu.
Istnieje lepszy sposób
Porównywanie chronione hasłem to funkcja GroupDocs.Comparison dla .NET, która otwiera zaszyfrowane pliki PDF, DOCX, XLSX i PPTX „na miejscu” i decyduje, które hasło chroni wynik porównania. Brak kroku odszyfrowania, brak pośrednich plików w czystym tekście: hasło podróżuje razem z dokumentem do samego porównania, jako właściwość w LoadOptions.
Zanim zaczniemy, będziesz potrzebować:
- .NET 8.0 SDK lub nowszy
- GroupDocs.Comparison 26.9.0 (tymczasowa licencja)
- Dwa zaszyfrowane dokumenty tego samego formatu oraz ich hasła
Instalacja jednym poleceniem:
dotnet add package GroupDocs.Comparison
Nowy sposób: Zaszyfrowane dokumenty prosto do porównywarki
Poniższy przykład porównuje dwa zaszyfrowane pliki PDF – źródło otwiera się hasłem 1234, a cel hasłem 4321 – i zapisuje jeden plik wynikowy ze zmianami wstawionymi w miejscu. Świadomie różne hasła, ponieważ to właśnie tam ukrywa się pierwszy błąd.
Krok 1 – Nadaj każdemu dokumentowi własne LoadOptions
Comparer przechowuje jedno źródło i dowolną liczbę celów, a każdy dokument ma własną ochronę. Hasło źródła przekazywane jest do konstruktora; hasło każdego celu przekazywane jest do jego własnego wywołania Add.
// Jedno LoadOptions na dokument – opcje konstruktora odblokowują
// tylko źródło i nigdy nie docierają do celów.
using var comparer = new Comparer("source.pdf",
new LoadOptions { Password = "1234" });
comparer.Add("target.pdf", new LoadOptions { Password = "4321" });
To jest szczegół, który łapie ludzi w pułapkę. Przekazanie jednego LoadOptions do konstruktora i oczekiwanie, że obejmie on cele, jest najczęstszą przyczyną błędu, a ze względu na moment wystąpienia awarii nie ogłasza się w miejscu, w którym byś jej szukał.
Krok 2 – Zdecyduj, co chroni wynik
CompareOptions.PasswordSaveOption wybiera ochronę wyniku: None, Source, Target lub User. Domyślnie jest None, co cicho zamienia dwa zaszyfrowane wejścia w jeden niechroniony wynik.
// Oznaczenia w miejscu, a wynik ponownie używa hasła dokumentu źródłowego.
var options = new PdfCompareOptions
{
DisplayMode = PdfCompareOptions.ComparisonDisplayMode.Inline,
PasswordSaveOption = PasswordSaveOption.Source
};
comparer.Compare("Result/1-pdf-inline.pdf", options);
Kluczowe punkty:
- PasswordSaveOption:
Sourceponownie używa hasła źródła w wyniku. WybierzUserwraz zSaveOptions.Password, aby nadać nowe hasło. - ComparisonDisplayMode: zagnieżdżone w
PdfCompareOptions, które oferuje takżeSideBySideiInterleaved.WordCompareOptionsdeklaruje własne wyliczenie o tej samej nazwie, ale z innymi wartościami, więc samodzielna nazwa nie skompiluje się – użyj pełnej kwalifikacji.
Krok 3 – Chroń wynik własnym hasłem
Gdy różnica trafia do recenzentów, którzy nie powinni znać żadnego z oryginalnych haseł, PasswordSaveOption.User pobiera wartość z SaveOptions.Password zamiast ponownie używać hasła wejściowego.
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);
Oba obiekty trafiają do przeciążenia Compare z trzema argumentami. Ustawienie samego SaveOptions.Password nic nie zmienia – to wartość wyliczenia aktywuje hasło po stronie zapisu. Wynik tego wywołania otwiera się hasłem 5678 i odrzuca 1234.
Dlaczego mój try/catch wokół Comparera nie łapie złego hasła?
Ponieważ konstruktor nigdy nie otwiera dokumentu. Rejestruje jedynie ścieżkę, podobnie robi Add. Oba dokumenty są odczytywane dopiero podczas wywołania Compare, i właśnie wtedy rzucany jest PasswordProtectedFileException z komunikatem Password is missing. Nieprawidłowe hasło zachowuje się identycznie: jest akceptowane w ciszy w czasie konstrukcji, a później odrzucane przy Compare.
Dlatego należy chronić wywołanie porównania, a nie konstruktor. Odkryłem to w wolny sposób, owijając konstrukcję w try i obserwując, jak zaszyfrowany plik przechodzi przez nią, dopiero trzy linie później wywołując błąd. Repozytorium wypisuje każdy etap, co czyni kolejność oczywistą przy pierwszym spojrzeniu:
using var comparer = new Comparer("source.pdf"); // udane
comparer.Add("target.pdf"); // udane
comparer.Compare("Result/unreachable.pdf"); // tutaj rzuca wyjątek
Obok siebie: Przed vs. Po
| Przed (najpierw odszyfrować) | Po (GroupDocs.Comparison) | |
|---|---|---|
| Kroki w potoku | Odszyfruj oba, porównaj, usuń tymczasowe kopie | Porównaj |
| Czysty tekst na dysku | Dwie kopie, sprzątanie przy każdej ścieżce błędu | Brak |
| Ochrona wyniku | Oddzielny krok ponownego szyfrowania | Jedna wartość PasswordSaveOption |
| Zakres formatów | Narzędzia odszyfrowujące per format | Jedno LoadOptions.Password dla PDF, DOCX, XLSX, PPTX |
| Wymagany kod | Pomocnik odszyfrowania + porównanie | 4 linie |
Funkcje porównywania nie zmieniają się przy zaszyfrowanym wejściu. Tryby wyświetlania, strony podsumowujące i wykrywanie stylów zachowują się dokładnie tak, jak przy plikach w czystym tekście, ponieważ ochrona jest obsługiwana w całości w warstwie ładowania.
To warstwowanie sprawia, że wsparcie formatów jest tanie. LoadOptions.Password to zwykła właściwość typu string, a ta sama właściwość odblokowuje PDF, DOCX, XLSX i PPTX – kod ładowania w przykładzie Word poniżej jest znak po znaku tym, co używają przykłady PDF. Zmienia się tylko klasa opcji i tylko dlatego, że każdy format udostępnia inne możliwości renderowania. Dodanie wsparcia zaszyfrowanych arkuszy kalkulacyjnych do kodu, który już porównuje zaszyfrowane PDF, nie kosztuje nic w ścieżce ładowania.
Przykład z życia: Redagowanie umów między kancelariami
Zespół prawny otrzymuje każdą wersję umowy zaszyfrowaną, przy czym hasło jest zmieniane przy każdej wymianie, aby wyciek hasła nie ujawnił całej historii. Partner recenzujący potrzebuje jednego oznaczonego dokumentu na rundę, a zgodnie z zasadami przechowywania oznaczona kopia nie może leżeć niechroniona na udostępnionym dysku.
Dwa ustawienia to pokrywają. Każdy dokument jest odblokowywany własnym LoadOptions, więc rotacja haseł nie wymaga specjalnej obsługi, a PasswordSaveOption.User nadaje każdej dystrybuowanej różnicy własne hasło – takie, które odblokowuje porównanie i nic więcej.
// Poprawki w Word, aby partner recenzujący mógł zaakceptować lub odrzucić każdą edycję.
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);
Co jeszcze możesz zrobić z GroupDocs.Comparison?
- Porównywać więcej niż dwa chronione dokumenty: dodaj kilka zaszyfrowanych celów do jednego porównania, dla formatów Word i prezentacji.
- Generować natywne poprawki w Word:
WordCompareOptions.ComparisonDisplayMode.Revisionszapisuje zmiany, które recenzent może zaakceptować lub odrzucić bezpośrednio w Wordzie. - Kontrolować ładowanie zasobów zewnętrznych: blokuj lub whitelistuj odwołania zdalne, które dokument zawiera, jako kolejny mechanizm ochronny w
LoadOptions. - Generować stronę podsumowania:
GenerateSummaryPagedodaje przegląd zmian do dokumentu wynikowego.
Podsumowanie
Objazd odszyfrowania nigdy nie dotyczył samego porównywania – dotyczył biblioteki, która nie potrafiła odczytać tego, co miałeś. Ustawienie LoadOptions.Password per dokument usuwa folder tymczasowy, ścieżki sprzątania i niechronioną różnicę na końcu łańcucha. Pozostały trzy decyzje: hasło per dokument, jawny PasswordSaveOption zamiast domyślnego None oraz obsługa błędów wokół Compare, gdzie faktycznie pojawia się awaria.
Gotowy, aby zautomatyzować swój przepływ dokumentów?
- Wypróbuj darmowy trial API
- Poznaj ładowanie dokumentów chronionych hasłem
- Przeczytaj pełny przewodnik po porównywaniu chronionych dokumentów
- Zobacz przykładowy projekt na GitHubie