콘텐츠로 이동

암호화: AES-256(CBC) 및 AES-256-GCM

Core는 ISO 32000-2:2020 §7.6 Standard 보안 핸들러에 따라 AES-256으로 PDF를 암호화합니다. 기본 모드는 V=5 / R=6 / AESV3(AES-256-CBC)입니다. 선택 모드는 ISO/TS 32003:2023 V=6 / R=7 AES-256-GCM 인증 경로입니다. 이 페이지는 키 유도, 와이어 형식, 권한 경계, 프로덕션 배포에서 반드시 이해해야 하는 제한 사항을 설명합니다.

Terminal window
composer require nextpdf/core:^3

기본 경로에는 openssl 확장이 필요합니다. AES-256-GCM 경로는 openssl 또는 ext-sodium을 사용합니다. AES-NI 하드웨어가 없는 호스트에서는 libsodium이 GCM을 거부하며, Core는 알고리즘을 약화시키지 않고 더 느린 OpenSSL 구현으로 폴백합니다.

기본 핸들러는 AESV3 암호화 필터를 사용하는 V=5 / R=6 Standard 보안 핸들러입니다. setEncryption() 호출 시 Core는 플랫폼 암호화 난수 소스(random_bytes())에서 무작위 256 비트 파일 키를 생성합니다. 바이트 길이는 32 바이트로, FIPS 197 키 길이와 일치합니다. 객체별 콘텐츠는 AES-256-CBC로 암호화됩니다. 16 바이트 초기화 벡터는 ISO 32000-2:2020 §7.6.4의 지시에 따라 각 암호문 앞에 추가됩니다.

키 유도는 개정 6의 Algorithm 2.B를 따릅니다. 비밀번호는 먼저 SASLprep(RFC 4013)으로 정규화된 다음, ISO 32000-2:2020 §7.6.4.3.3의 지시에 따라 문자 경계에서 127 UTF-8 바이트로 잘립니다. 유도된 해시는 AES-128-CBC 단계로 구동되는 반복 SHA-256 / SHA-384 / SHA-512 루틴으로 계산되며, 이는 오프라인 비밀번호 추측 비용을 높입니다. 사용자, 소유자 및 키별 솔트는 암호화기 인스턴스당 한 번 생성되므로, 단일 인스턴스에서는 딕셔너리 바이트가 결정론적으로 출력됩니다. 이는 다중 패스 작성기의 전제 조건입니다.

useAesGcm() 메서드는 선택적 AES-256-GCM 경로를 켭니다. 이 경로는 ISO/TS 32003:2023 V=6 / R=7 AESV4 암호화 필터를 구현합니다. 암호는 NIST SP 800-38D의 매개변수를 사용하는 AES-256-GCM입니다. 암호화된 객체별 와이어 레이아웃은 12 바이트 IV, 암호문, 그다음 16 바이트 인증 태그입니다. 추가 인증 데이터는 TS 32003 §5.2 프로파일의 지시에 따라 비어 있습니다. 복호화는 태그를 검증하고, 불일치하면 TamperedDataException을 발생시키며, 태그 검증에 실패한 경우 절대 평문을 반환하지 않습니다. 이 경로는 기본 CBC 경로가 자체적으로 제공하지 않는 변조 감지 기능을 추가합니다.

GCM 경로의 IV 고유성 규칙은 NIST SP 800-38D §8을 따릅니다. IV의 상위 4 바이트는 생성 시 난수 소스에서 설정되는 인스턴스별 고정 필드입니다. 하위 8 바이트는 IV가 발급될 때마다 증가하는 빅엔디언 카운터입니다. 이는 §8.2.1의 결정론적 구성 방식과 일치하지만, 고정 필드를 순차 열거하지 않고 문서 간 충돌을 방지하기 위해 무작위화한다는 점이 다릅니다. 두 번째 보호 장치는 발급된 모든 IV를 충돌 집합에 기록하며, 값이 반복되면 NonceReuseException을 발생시킵니다. 카운터 롤오버도 NonceReuseException을 발생시키는데, 롤오버는 §8이 경고하는 IV 재사용 실패 모드이기 때문입니다.

GCM 경로에는 두 가지 길이 제한이 적용됩니다. 객체별 평문 상한은 2^39 − 256 바이트로, NIST SP 800-38D §5.2.1.1에서 유도된 호출별 제한입니다. 더 큰 입력은 객체별로 분할하라는 안내와 함께 길이 예외를 발생시킵니다. 호출 안전 제한은 키당 2^32회 호출입니다. assertWithinSafetyBound()는 §8.3 임계값에 도달하기 전에 호출자가 문서 키를 순환하도록 GcmInvocationLimitExceededException을 발생시키는 선택적 검사입니다. NIST SP 800-57 Part 1 §4는 이 키 수명 결정을 배포 책임으로 규정합니다.

권한 플래그는 권고 사항입니다. 비트마스크는 암호화된 /Perms 항목과 /P 값에 기록되며, 읽을 때 validatePerms()로 복원됩니다. 마커가 손상된 경우에는 안전하게 실패합니다. 규격을 준수하는 리더는 이 플래그를 존중할 것으로 예상됩니다. 암호화가 이 플래그를 강제하지는 않습니다. 복호화 키를 가지고 비트를 무시하는 프로세서는 콘텐츠를 읽거나 복사하거나 수정할 수 있습니다. 권한 플래그는 접근 제어가 아니라 리더 규약으로 설명하세요.

유형종류주요 멤버안정성도입 버전
Aes256Encryptorclassencrypt(), decrypt(), encryptForObject(), buildEncryptionDictionary(), verifyUserPassword(), verifyOwnerPassword(), validatePerms(), getEncryptionKey()안정1.0.0
Aes256GcmEncryptorclassencrypt(), decrypt(), encryptStream(), assertWithinSafetyBound(), invocationCount(), isAvailable()안정2.18.0
KeyMaterialfinal readonly classgenerate(), exposeKey(), fingerprint()안정2.18.0
EncryptedPayloadSpecfinal readonly classtoDict()안정2.18.0
CryptoCapabilitiesfinal classhasAesGcm(), detectFipsMode(), assertFipsAvailableForProfile()안정2.0.0
NonceReuseException예외안정2.18.0
TamperedDataException예외안정2.18.0
DecryptionFailedException예외안정2.18.0
GcmInvocationLimitExceededException예외안정3.0.0
examples/22-protection.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// AES-256-CBC, V=5/R=6. Call before addPage().
$doc->setEncryption(
userPassword: 'demo',
ownerPassword: 'admin',
permissions: 4, // printing only; copy/modify denied for a conforming reader
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 8, 'Confidential', newLine: true);
$doc->save(__DIR__ . '/output/22-protection.pdf');
examples/security/gcm-authenticated-encryption.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Security\CryptoCapabilities;
use NextPDF\Security\Encryption\Aes256GcmEncryptor;
use NextPDF\Security\Exception\TamperedDataException;
use NextPDF\Security\KeyMaterial;
use Psr\Log\LoggerInterface;
final readonly class AuthenticatedBlobCipher
{
public function __construct(private LoggerInterface $logger) {}
/**
* Seal a payload with AES-256-GCM and return the wire-format bytes.
*
* @param non-empty-string $plaintext The payload to protect.
*
* @return non-empty-string IV(12) || ciphertext || tag(16).
*/
public function seal(string $plaintext, KeyMaterial $key): string
{
if (!CryptoCapabilities::hasAesGcm()) {
throw new \RuntimeException('Host cannot perform AES-256-GCM.');
}
$cipher = new Aes256GcmEncryptor($key);
// Opt-in NIST SP 800-38D §8.3 key-rotation guard.
$cipher->assertWithinSafetyBound();
$wire = $cipher->encrypt($plaintext);
$this->logger->info('Payload sealed', [
'key_fingerprint' => $key->fingerprint(),
'invocations' => $cipher->invocationCount(),
]);
return $wire;
}
/**
* Open a sealed payload; a modified payload raises, never returns plaintext.
*
* @param non-empty-string $wire IV(12) || ciphertext || tag(16).
*/
public function open(string $wire, KeyMaterial $key): string
{
try {
return (new Aes256GcmEncryptor($key))->decrypt($wire);
} catch (TamperedDataException $e) {
$this->logger->warning('Tampered payload rejected', [
'key_fingerprint' => $key->fingerprint(),
]);
throw $e;
}
}
}

이 암호 구현은 호스트 기능을 확인하고, 선택적 호출 가드를 적용하며, 되돌릴 수 없는 키 지문만 로깅합니다. 변조가 감지되면 의심스러운 바이트를 반환하지 않고 거부 예외를 다시 던집니다.

  • 기본 AES-256-CBC 경로는 기밀성 전용입니다. 이 경로는 자체적으로 변조된 암호문을 감지하지 않습니다. 변조 감지가 필요한 경우 AES-256-GCM 경로를 사용하세요.
  • useAesGcm()은 PDF/A 모드가 활성화되어 있거나 opensslext-sodium 모두 AES-256-GCM을 제공하지 않을 때 예외를 발생시킵니다. 두 경우를 모두 캐치하여 운영자가 조치할 수 있는 메시지를 표시하세요.
  • AES-NI가 없는 호스트에서는 libsodium이 GCM을 거부합니다. Core는 OpenSSL GCM으로 폴백하는데, 이 구현은 정확하게 동작하지만 더 느립니다. 처리량은 떨어지지만 보안 수준은 떨어지지 않습니다.
  • GCM 객체별 평문 상한은 2^39 − 256 바이트입니다. 더 큰 입력은 길이 예외를 발생시킵니다. encryptStream()을 사용하여 콘텐츠를 여러 객체로 분할하세요.
  • 하나의 KeyMaterial 인스턴스는 정확히 32 바이트여야 합니다. 길이가 잘못된 경우 잘리지 않고 생성 시 거부됩니다.
  • 리더 경로(verifyUserPassword(), verifyOwnerPassword(), validatePerms())는 암호화 자료에 대해 상수 시간 비교를 사용하며 손상된 권한 마커에 대해 안전하게 실패합니다.

객체별 AES-256-CBC 암호화는 한 번의 OpenSSL 호출이며, 객체 본문 크기에 대해 O(n)입니다. 키 유도는 암호화기 인스턴스당 한 번 반복 Algorithm 2.B 루틴을 실행하며, 비용은 제한적이고 문서당 일정합니다. AES-256-GCM 스트리밍 경로는 입력을 16 MiB 청크로 분할하여, 전체 입력 크기와 관계없이 라이브 힙을 약 64 MB로 제한합니다. 이는 문서화된 64 MB 피크 예산보다 충분히 낮습니다. 각 GCM 객체는 28 바이트의 오버헤드(12 바이트 IV와 16 바이트 태그)를 추가합니다. AES-NI 하드웨어는 GCM 처리량을 실질적으로 향상시키며, 이것이 없으면 처리량만 낮아집니다.

이 API 표면의 위협 모델은 명시적입니다. 오프라인 비밀번호 추측은 SASLprep 정규화와 반복 개정 6 키 유도로 비용이 높아지지만, 약한 비밀번호는 여전히 가장 큰 잔여 위험으로 남아 있습니다. 어떤 유도 방식으로도 이를 제거할 수 없습니다. 암호문 변조는 GCM 경로에서 태그 검증을 통해 감지되며, 기본 CBC 경로에서는 감지되지 않습니다. GCM 경로의 IV 재사용은 카운터와 충돌 집합으로 방지되며, 이는 NIST SP 800-38D §8.1 IV 규칙과 일치합니다. 카운터 롤오버는 순환하지 않고 거부됩니다. 로그를 통한 키 노출은 KeyMaterial 마스킹과 비밀번호에 대한 #[\SensitiveParameter] 어트리뷰트로 완화됩니다. 유도된 키 자료는 플랫폼이 허용하는 경우 사용 후 0으로 채워집니다.

경계도 명확합니다. AES-256 암호화는 ISO 32000-2:2020 §7.6에 정의된 대로 적용되며, 선택 경로의 경우 ISO/TS 32003:2023 §5.2에 따라 적용됩니다. 효과적인 보호는 비밀번호 강도, 키 관리, 배포 환경, 콘텐츠를 읽는 리더에 따라 달라집니다. 권한 플래그는 규격을 준수하는 리더에 의해 존중되며 암호화로 강제되지 않습니다. /Perms 값에 사용되는 AES-ECB 단계는 단일 16 바이트 블록에 대해 ISO 32000-2:2020 §7.6.4.4.10에서 의무화합니다. 이는 범용 모드가 아닙니다. 2^32 호출 제한에 도달하기 전에 키를 순환하는 것은 배포 책임이며, Core는 이에 대한 검사를 제공하지만 기본적으로 강제하지는 않습니다.

암호화와 복호화는 프로세스 내에서 실행됩니다. 이 API 표면에서는 문서 바이트, 비밀번호 또는 키 값이 호스트를 벗어나지 않습니다. GCM IV 충돌 집합은 키 바이트 대신 되돌릴 수 없는 키 지문을 맵 키로 사용합니다. 외부 키 관리 또는 PKCS#11 토큰으로 키 보관을 처리하는 배포 환경은 해당 백엔드의 레지던시를 책임집니다. OASIS PKCS#11 v3.1 C_GenerateKey는 토큰 상주 키 생성을 위한 계약 지점입니다.

안전한 텔레메트리 및 로그 스크러빙

섹션 제목: “안전한 텔레메트리 및 로그 스크러빙”

정책 이름과 8자 키 지문을 로깅하되, 키나 비밀번호는 절대 로깅하지 마세요. KeyMaterial::__toString()__debugInfo()는 마스킹된 자리 표시자를 반환합니다. 이 API 표면의 예외 메시지는 키 바이트가 아니라 작업 레이블과 지문을 포함합니다. GCM 호출 횟수는 키 순환 대시보드를 위한 안전한 텔레메트리 신호입니다.

위협Core의 완화 방안잔여 경계
오프라인 비밀번호 추측SASLprep 및 반복적인 개정 6 유도약한 비밀번호가 여전히 가장 큰 위험
암호문 변조GCM 태그 검증(선택적 경로)CBC 경로는 기밀성 전용
IV 재사용(GCM)무작위 고정 필드, 카운터, 충돌 집합. 롤오버는 거부
너무 긴 GCM 평문길이 검사 2^39 − 256. 분할 안내호출자는 큰 입력을 스트리밍해야 함
키 과다 사용(GCM)assertWithinSafetyBound(), 한도 2^32선택 사항. 기본적으로 강제되지 않음
권한 플래그 우회없음 — 플래그는 권고 사항규격을 준수하지 않는 리더는 플래그를 무시함
로그를 통한 키 노출KeyMaterial 마스킹; #[\SensitiveParameter]exposeKey()를 로깅하는 호출자는 모두 이를 무력화함

Core는 FIPS 검증된 암호화 모듈이 아니며 FIPS 인증을 받지 않았습니다. CryptoCapabilities::detectFipsMode()는 활성, 부재 또는 불확정을 보고하는 최선 노력 프로브이며, assertFipsAvailableForProfile()은 FIPS 공급자를 입증하지 못하는 호스트에서 FIPS 프로파일이 선택된 경우 안전하게 실패합니다. 암호화 표면은 FIPS 검증된 공급자를 로드한 호스트 OpenSSL 빌드에서 실행될 때 FIPS 호환 모드로 작동합니다. 검증 및 인증된 운영 태세는 Enterprise의 영역입니다.

주장표준조항증거
모든 GCM IV는 결정론적 고정 필드 및 카운터 구성을 통해 호출마다 고유합니다.NIST SP 800-38D§8.2.1
IV 구성 규칙은 하나의 키에 대한 호출 간 재사용을 방지합니다.NIST SP 800-38D§8.1
객체별 평문 상한은 호출별 길이 제한과 일치합니다.NIST SP 800-38D§5.2.1.1
키 암호 사용 기간과 순환은 배포 책임입니다.NIST SP 800-57 Part 1 Rev. 5 표준§4
AES 파일 키는 256 비트로, 표준의 키 길이와 일치합니다.FIPS 197§4.2.1
토큰 상주 키 생성은 외부 키 스토어 통합 지점입니다.OASIS PKCS#11 v3.1 표준C_GenerateKey 함수

ISO 32000-2:2020 §7.6과 ISO/TS 32003:2023 §5.2는 여기에 문서화된 핸들러의 규범적 근거입니다. 해당 본문에는 라이선스 제한이 있습니다. 이 페이지는 이를 의역하고 조항 번호를 인용하며, 어느 것도 직접 인용하지 않습니다. 바이트 단위로 정확한 키 유도에 대한 검증된 런타임 증거는 Algorithm 2.B 표준 테스트와 페이지 증거 트레일러에 나열된 외부 오라클 픽스처입니다.

Core는 로컬 키 표면 및 암호화 정책 게이트와 함께 기본 AES-256-CBC 경로와 선택적 AES-256-GCM 경로를 모두 제공합니다. Enterprise 에디션은 동일한 계약 뒤에서 HSM/PKCS#11 키 보관 백엔드와 FIPS 모드 암호화 정책 프로파일을 추가합니다. 공개 API는 동일하며, 키 보관 백엔드와 정책 구현이 다릅니다.