💡 전체 작동 예제는 GitHub에서 확인할 수 있습니다:
compare-encrypted-pdf-and-word-documents-dotnet
이전 방식은 고통스러웠다
두 개의 공급 계약 개정본이 받은 편지함에 도착합니다. 두 파일 모두 비밀번호로 보호되어 있으며, 비밀번호가 서로 다릅니다. 그리고 누군가는 변경 사항을 표시한 사본이 필요합니다. 사용 중인 비교 라이브러리는 평문 입력을 기대하므로 파이프라인에 단계가 추가됩니다: 두 파일을 임시 폴더에 복호화하고, 평문 사본을 비교한 뒤, 이를 삭제합니다. 그 임시 폴더는 문서가 민감하기 때문에 존재하는 워크플로우에서 가장 약한 고리가 됩니다.
같은 문제의 두 번째 버전은 놓치기 쉽습니다. 일부 팀은 임시 폴더를 건너뛰고 대신 메모리에서 복호화하는데, 이는 정리 문제는 해결하지만 형식 문제는 해결하지 못합니다: 복호화 API가 형식마다 다르기 때문에 암호화된 PDF 뒤에 암호화된 스프레드시트를 지원하려면 두 번째 통합이 필요합니다, 두 번째 코드 라인이 아니라요.
비용은 주로 복호화 호출 자체에 있는 것이 아니라 그 주변에 있습니다. 평문 사본을 어딘가에 기록하고, 모든 종료 경로(실패 경로 포함)에서 정리하고, 백업 및 크래시 덤프에 포함되지 않도록 해야 합니다. 이렇게 생성된 차이점도 기본적으로 보호되지 않으므로, 두 개의 암호화된 입력이 하나의 파일로 변환되어 누구나 열 수 있게 됩니다.
복호화 우회의 실제 비용: 이유가 있어 암호화된 문서의 평문 사본을 보관하는 임시 디렉터리와, 모든 오류 경로에서 올바르게 정리해야 하는 작업.
더 나은 방법이 있다
비밀번호로 보호된 비교는 .NET용 GroupDocs.Comparison 기능으로, PDF, DOCX, XLSX, PPTX 파일을 제자리에서 열고 비교 결과를 어떤 비밀번호가 보호할지 결정합니다. 복호화 단계도, 평문 중간 파일도 없습니다: 비밀번호가 LoadOptions의 속성으로 문서와 함께 비교로 전달됩니다.
시작하기 전에 다음이 필요합니다:
- .NET 8.0 SDK 이상
- GroupDocs.Comparison 26.9.0 (임시 라이선스)
- 동일한 형식의 두 개 암호화된 문서와 그 비밀번호
한 명령으로 설치합니다:
dotnet add package GroupDocs.Comparison
새로운 방식: 암호화된 문서를 바로 비교기에 전달
아래 예제는 두 개의 암호화된 PDF를 비교합니다 – 소스는 1234로, 대상은 4321로 열고, 변경 사항을 인라인으로 병합한 단일 결과 파일을 작성합니다. 비밀번호가 서로 다른 이유는 첫 번째 실수가 여기서 숨겨져 있기 때문입니다.
단계 1 - 각 문서에 자체 LoadOptions 지정
Comparer는 하나의 소스와 여러 대상을 보유하며, 각 문서는 자체 보호 정보를 가집니다. 소스의 비밀번호는 생성자에 전달하고, 각 대상의 비밀번호는 해당 Add 호출에 전달합니다.
// 문서당 하나의 LoadOptions – 생성자의 옵션은 소스만 잠금 해제하고,
// 대상에는 절대 도달하지 않는다.
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이며, 이는 두 개의 암호화된 입력을 하나의 보호되지 않은 결과로 조용히 변환합니다.
// 인라인 마크업, 결과는 소스 문서의 비밀번호를 재사용한다.
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);
두 객체는 세 개 인자를 받는 Compare 오버로드에 전달됩니다. SaveOptions.Password만 설정해도 아무 변화가 없으며, 열거형 값이 저장 측 비밀번호 적용을 활성화합니다. 이 호출의 결과 파일은 5678로 열리고 1234는 거부됩니다.
왜 Comparer 주변의 try/catch가 잘못된 비밀번호를 잡지 못할까?
생성자는 문서를 열지 않기 때문입니다. 경로만 기록하고, Add도 마찬가지입니다. 두 문서는 Compare 실행 시에 읽히며, 그때 PasswordProtectedFileException(메시지: Password is missing)이 발생합니다. 잘못된 비밀번호도 동일하게 동작합니다: 생성 시에는 조용히 받아들이고, Compare 단계에서 거부됩니다.
따라서 비교 호출을 보호해야지 생성자를 보호하면 안 됩니다. 저는 처음에 try로 생성자를 감싸고, 암호화된 파일이 세 줄 뒤에 실패하기 전까지 통과하는 모습을 보며 이 문제를 발견했습니다. 저장소는 각 단계를 출력해 첫 번째 읽기에서 순서를 명확히 보여줍니다:
using var comparer = new Comparer("source.pdf"); // 성공
comparer.Add("target.pdf"); // 성공
comparer.Compare("Result/unreachable.pdf"); // 여기서 예외 발생
나란히 보기: 이전 vs. 이후
| 이전 (먼저 복호화) | 이후 (GroupDocs.Comparison) | |
|---|---|---|
| 파이프라인 단계 | 두 파일 복호화 → 비교 → 임시 사본 삭제 | 비교 |
| 디스크에 평문 | 두 사본, 모든 오류 경로에서 정리 필요 | 없음 |
| 결과 보호 | 별도의 재암호화 단계 | 하나의 PasswordSaveOption 값 |
| 형식 지원 | 형식별 복호화 도구 필요 | PDF, DOCX, XLSX, PPTX에 대해 하나의 LoadOptions.Password |
| 필요 코드 | 복호화 도우미 + 비교 | 4줄 |
암호화된 입력에서도 비교 기능은 변하지 않습니다. 표시 모드, 요약 페이지, 스타일 감지는 평문 파일과 동일하게 동작합니다. 보호는 전적으로 로딩 레이어에서 처리되기 때문입니다.
이 레이어링 덕분에 형식 지원이 저렴합니다. LoadOptions.Password는 단순 string 속성이고, 같은 속성이 PDF, DOCX, XLSX, PPTX를 모두 해제합니다 – 아래 Word 예제의 로딩 코드는 PDF 예제와 문자 그대로 동일합니다. 옵션 클래스만 형식마다 다른 렌더링 선택지를 제공할 뿐입니다. 이미 암호화된 PDF를 비교하는 코드에 암호화된 스프레드시트 지원을 추가해도 로딩 경로에서는 비용이 전혀 들지 않습니다.
실제 사례: 로펌 간 계약 교정
법무팀은 각 개정본을 암호화해서 받으며, 교환마다 비밀번호를 교체해 유출 시 전체 기록이 노출되지 않게 합니다. 검토 파트너는 라운드마다 하나의 마크업 문서가 필요하고, 보존 규정에 따라 마크업 사본이 파일 공유에 보호되지 않은 상태로 남아서는 안 됩니다.
두 가지 설정으로 이를 해결합니다. 각 문서는 자체 LoadOptions로 잠금 해제되므로 비밀번호 교체에 별도 처리가 필요 없고, PasswordSaveOption.User는 배포된 각 차이점에 자체 비밀번호를 부여합니다 – 비교를 열 수는 있지만 그 외에는 아무 것도 할 수 없는 비밀번호입니다.
// Word 개정본, 검토 파트너가 각 편집을 수락하거나 거부할 수 있도록.
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으로 할 수 있는 다른 일은?
- 보호된 문서 두 개 이상 비교: 하나의 비교에 여러 암호화된 대상 추가, Word 및 프레젠테이션 형식 지원.
- 네이티브 Word 개정본 생성:
WordCompareOptions.ComparisonDisplayMode.Revisions는 리뷰어가 Word 자체에서 변경을 수락·거부하도록 기록합니다. - 외부 리소스 로딩 제어: 문서가 포함하는 원격 참조를 차단하거나 허용 목록에 추가하는 또 다른
LoadOptions보호 기능. - 요약 페이지 생성:
GenerateSummaryPage는 결과 문서에 변경 개요를 추가합니다.
결론
복호화 우회는 비교 자체가 아니라, 읽을 수 없는 라이브러리 때문이었습니다. 문서마다 LoadOptions.Password를 설정하면 임시 폴더, 정리 경로, 체인 끝의 보호되지 않은 차이점이 사라집니다. 남은 세 가지 결정만 있으면 됩니다: 문서당 비밀번호, 기본 None 대신 명시적인 PasswordSaveOption, 그리고 실제 실패가 발생하는 Compare 주변의 오류 처리.
문서 워크플로우를 자동화할 준비가 되셨나요?