Technical Article

대용량 문서를 위한 고속 AES-256 PDF 암호화

법률, 금융 및 의료 부문에서 대량의 PDF 문서를 생성하는 것은 일반적인 관행입니다. 그러나 문서를 생성하는 것은 절반의 성공에 불과하며 이를 보호하는 것도 똑같이 중요합니다. 매일 수백 기가바이트의 PDF를 처리하는 아카이브 파이프라인이 있을 때 AES-256 암호화를 적용하면 금방 성능 병목 현상이 발생할 수 있습니다.

이 기사에서는 메모리 고갈을 피하고 암호화 루프를 최적화하여 Delphi에서 고속 AES-256 PDF 암호화를 달성하는 방법을 살펴봅니다.

PDF 암호화 사양

PDF 보안은 크게 발전했습니다. 초기 버전은 40비트 RC4를 사용했는데, 오늘날에는 아주 쉽게 깨질 수 있습니다. 현재 표준(PDF 1.7 확장 수준 3 및 PDF 2.0)은 AES-256 암호화를 요구합니다.

PDF에서는 파일 전체 블록을 암호화하지 않습니다. 문서 구조(XRef 테이블 및 딕셔너리 구조)는 일반 텍스트로 유지됩니다. 대신 스트림(Streams)(이미지 및 페이지 콘텐츠의 원시 데이터)과 문자열(Strings)(예: 메타데이터 텍스트)을 암호화합니다. 이를 위해서는 파서가 데이터를 추출하고 AES CBC(암호 블록 체인)를 적용한 후 다시 기록해야 합니다.

병목 현상: 메모리로의 로딩

대용량 PDF(예: 2GB 스캔 아카이브)를 암호화할 때 흔히 저지르는 실수는 암호화 엔진에 전달하기 전에 전체 스트림을 TMemoryStream에 로드하는 것입니다. 이는 32비트 프로세스에서는 메모리 부족(OOM) 예외를 발생시키고 64비트 프로세스에서는 엄청난 페이지 폴트를 일으킵니다.

Delphi에서의 스트리밍 암호화

해결책은 청크(chunked)된 스트리밍 접근 방식을 사용하는 것입니다. Windows Cryptography API: Next Generation(CNG) 또는 OpenSSL과 같은 라이브러리를 사용하면 64KB 블록으로 PDF 스트림을 읽고 블록을 암호화한 후 출력 디스크 스트림에 직접 쓸 수 있습니다.

다음은 스트림에 대한 버퍼링된 암호화 루프를 보여주는 개념적 Delphi 예제입니다.

uses
  System.Classes, System.SysUtils;

const
  BUFFER_SIZE = 65536; // 64KB chunks

// This represents your AES-256 encryption routine
procedure EncryptStreamChunk(const InBuffer; var OutBuffer; BytesRead: Integer; const Key: TBytes; const IV: TBytes);
begin
  // Call to Windows CNG (BCryptEncrypt) or OpenSSL (EVP_EncryptUpdate)
  // ...
end;

procedure EncryptLargePDFStream(InputStream, OutputStream: TStream; const Key, IV: TBytes);
var
  InBuffer, OutBuffer: array of Byte;
  BytesRead: Integer;
begin
  SetLength(InBuffer, BUFFER_SIZE);
  // AES CBC requires padding, so the output buffer must be slightly larger
  SetLength(OutBuffer, BUFFER_SIZE + 16); 

  InputStream.Position := 0;
  OutputStream.Position := 0;

  repeat
    BytesRead := InputStream.Read(InBuffer[0], BUFFER_SIZE);
    if BytesRead > 0 then
    begin
      EncryptStreamChunk(InBuffer[0], OutBuffer[0], BytesRead, Key, IV);
      // Write the encrypted cipher text directly to disk
      OutputStream.Write(OutBuffer[0], BytesRead); // Note: padding logic omitted for brevity
    end;
  until BytesRead < BUFFER_SIZE;
end;

암호화 백엔드 최적화

Delphi 개발자는 AES 백엔드에 대해 여러 가지를 선택할 수 있습니다.

  • 기본 Delphi 구현: 배포가 쉽지만 순수하게 소프트웨어로 실행되므로 종종 느립니다.
  • Windows CNG(BCrypt): 고도로 최적화되어 있으며 하드웨어 가속(최신 Intel/AMD CPU의 AES-NI 명령어)을 활용할 수 있습니다.
  • OpenSSL(libcrypto): 업계 표준이며 믿을 수 없을 정도로 빠르지만 외부 DLL을 포함하여 배포해야 합니다.

처리가 많은 서버 애플리케이션의 경우 하드웨어 가속 AES-NI가 필수적입니다. Delphi에서 Windows CNG를 사용할 때 BCryptEncrypt 함수를 매핑하면 애플리케이션에서 무거운 작업을 CPU의 전용 암호화 실리콘에 오프로드하여 암호화 오버헤드를 거의 0으로 줄일 수 있습니다.

결론

기가바이트 규모의 PDF를 암호화할 때는 전체 메모리 버퍼링보다 스트림 청크에 의존하고 암호화 백엔드에서 하드웨어 AES-NI 가속을 활용하는지 확인해야 합니다. 이 조합을 통해 아카이브 파이프라인이 CPU에 얽매이지 않고 NVMe 드라이브의 속도로 실행되도록 보장할 수 있습니다.

참고: 청크 스트리밍을 활용한 고속 AES-256 암호화는 HotPDF VCL Component에서 기본적으로 지원됩니다.