Bỏ qua để đến nội dung

Mã hóa: AES-256 (CBC) và AES-256-GCM

Core mã hóa tệp Portable Document Format (PDF) bằng AES-256 (Advanced Encryption Standard với khóa 256 bit) theo trình xử lý bảo mật chuẩn của ISO 32000-2:2020 §7.6. Chế độ mặc định là V=5 / R=6 / AESV3 (AES-256-CBC, Cipher Block Chaining). Chế độ xác thực tùy chọn là đường dẫn AES-256-GCM (Galois/Counter Mode) V=6 / R=7 theo ISO/TS 32003:2023. Trang này định nghĩa dẫn xuất khóa, định dạng truyền, ranh giới quyền và các giới hạn của phần triển khai.

Terminal window
composer require nextpdf/core:^3

Đường dẫn mặc định yêu cầu tiện ích mở rộng openssl. Đường dẫn AES-256-GCM dùng openssl hoặc ext-sodium. Trên máy chủ không có phần cứng AES-NI, libsodium từ chối GCM; Core chuyển sang phần triển khai OpenSSL chậm hơn mà không thay đổi thuật toán.

Mặc định, Core dùng trình xử lý bảo mật chuẩn V=5 / R=6 với bộ lọc mật mã AESV3. Khi bạn gọi setEncryption(), Core sinh một khóa tệp 256 bit ngẫu nhiên từ nguồn ngẫu nhiên mật mã của nền tảng (random_bytes()). Khóa dài 32 byte, khớp với độ dài khóa của FIPS 197. Core mã hóa nội dung của từng đối tượng bằng AES-256-CBC. Core đặt vector khởi tạo 16 byte ở đầu mỗi bản mã, theo chỉ dẫn của ISO 32000-2:2020 §7.6.4.

Dẫn xuất khóa tuân theo Algorithm 2.B tại bản sửa đổi 6. Trước tiên Core chuẩn hóa mật khẩu bằng SASLprep (RFC 4013), rồi cắt còn 127 byte UTF-8 trên ranh giới ký tự, theo chỉ dẫn của ISO 32000-2:2020 §7.6.4.3.3. Core tính giá trị băm dẫn xuất bằng một quy trình SHA-256 / SHA-384 / SHA-512 lặp, được điều khiển bởi một bước AES-128-CBC, để làm tăng chi phí đoán mật khẩu ngoại tuyến. Core sinh các giá trị salt của người dùng, chủ sở hữu và từng khóa một lần cho mỗi thực thể bộ mã hóa, nên cùng một thực thể phát ra các byte từ điển một cách tất định; đây là điều kiện tiên quyết cho trình ghi nhiều lượt.

useAesGcm() bật đường dẫn AES-256-GCM tùy chọn. Đường dẫn này triển khai bộ lọc mật mã AESV4 V=6 / R=7 theo ISO/TS 32003:2023. Thuật toán mã hóa là AES-256-GCM với các tham số từ NIST SP 800-38D. Với mỗi đối tượng được mã hóa, bố cục truyền gồm một IV 12 byte, bản mã và một thẻ xác thực 16 byte. Dữ liệu xác thực bổ sung để trống, theo chỉ dẫn của hồ sơ TS 32003 §5.2. Quá trình giải mã xác minh thẻ và phát sinh TamperedDataException khi không khớp; nó không bao giờ trả về văn bản gốc sau khi thẻ thất bại. Đường dẫn này bổ sung khả năng phát hiện sửa đổi mà bản thân đường dẫn CBC mặc định không cung cấp.

Đường dẫn GCM tuân theo nguyên tắc IV duy nhất trong NIST SP 800-38D §8. 4 byte cao của IV là một trường cố định cho mỗi thực thể, được đặt từ một nguồn ngẫu nhiên trong quá trình khởi tạo. 8 byte thấp là một bộ đếm big-endian, được tăng sau mỗi IV được cấp. Cách này tuân theo phương pháp dựng có tính tất định trong §8.2.1, ngoại trừ việc trường cố định được ngẫu nhiên hóa để ngăn va chạm giữa các tài liệu thay vì được liệt kê. Một biện pháp bảo vệ thứ hai ghi lại mọi IV đã phát ra vào một tập va chạm và phát sinh NonceReuseException nếu một giá trị lặp lại. Tràn bộ đếm cũng phát sinh NonceReuseException, vì tràn là dạng lỗi tái sử dụng IV mà §8 cảnh báo.

Hai giới hạn độ dài áp dụng cho đường dẫn GCM. Giới hạn trên của văn bản gốc cho mỗi đối tượng là 2^39 − 256 byte, tức giới hạn cho mỗi lần gọi được suy ra trong NIST SP 800-38D §5.2.1.1. Đầu vào lớn hơn sẽ phát sinh một ngoại lệ độ dài kèm hướng dẫn phân chia dữ liệu qua nhiều đối tượng. Giới hạn an toàn về số lần gọi là 2^32 lần gọi cho mỗi khóa. assertWithinSafetyBound() là một bước kiểm tra tùy chọn phát sinh GcmInvocationLimitExceededException, cho phép bên gọi xoay khóa tài liệu trước ngưỡng §8.3. NIST SP 800-57 Part 1 §4 coi quyết định về vòng đời khóa này là trách nhiệm của phần triển khai.

Các cờ quyền chỉ mang tính khuyến nghị. Core ghi mặt nạ bit vào mục /Perms đã mã hóa và giá trị /P, rồi khôi phục bằng validatePerms() khi đọc; thao tác này sẽ thất bại đóng (fail closed) nếu có dấu hiệu bị hỏng. Một trình đọc tuân thủ được kỳ vọng tôn trọng các cờ này. Các cờ này không được thực thi bằng mật mã: một bộ xử lý có khóa giải mã và bỏ qua các bit này có thể đọc, sao chép hoặc sửa đổi nội dung. Hãy mô tả các cờ quyền như một quy ước của trình đọc, không phải cơ chế kiểm soát truy cập.

KiểuLoạiThành viên chínhĐộ ổn địnhTừ phiên bản
Aes256Encryptorclassencrypt(), decrypt(), encryptForObject(), buildEncryptionDictionary(), verifyUserPassword(), verifyOwnerPassword(), validatePerms(), getEncryptionKey()ổn định1.0.0
Aes256GcmEncryptorclassencrypt(), decrypt(), encryptStream(), assertWithinSafetyBound(), invocationCount(), isAvailable()ổn định2.18.0
KeyMaterialfinal readonly classgenerate(), exposeKey(), fingerprint()ổn định2.18.0
EncryptedPayloadSpecfinal readonly classtoDict()ổn định2.18.0
CryptoCapabilitiesfinal classhasAesGcm(), detectFipsMode(), assertFipsAvailableForProfile()ổn định2.0.0
NonceReuseExceptionexceptionổn định2.18.0
TamperedDataExceptionexceptionổn định2.18.0
DecryptionFailedExceptionexceptionổn định2.18.0
GcmInvocationLimitExceededExceptionexceptionổn định3.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;
}
}
}

Bộ mã hóa kiểm tra khả năng của máy chủ, áp dụng biện pháp bảo vệ số lần gọi tùy chọn, chỉ ghi nhật ký vân tay khóa không thể đảo ngược, và ném lại các từ chối do giả mạo thay vì trả về các byte đáng ngờ.

  • Đường dẫn AES-256-CBC mặc định chỉ cung cấp tính bảo mật. Tự nó không phát hiện bản mã bị sửa đổi. Hãy dùng đường dẫn AES-256-GCM khi bạn cần phát hiện sửa đổi.
  • useAesGcm() phát sinh khi chế độ PDF/A đang hoạt động và khi cả openssl lẫn ext-sodium đều không cung cấp AES-256-GCM. Hãy bắt cả hai trường hợp và hiển thị thông báo mà người vận hành có thể xử lý.
  • Trên máy chủ không có AES-NI, libsodium từ chối GCM. Core chuyển sang OpenSSL GCM, vốn đúng nhưng chậm hơn; thông lượng giảm, không phải tính bảo mật.
  • Giới hạn trên của văn bản gốc cho mỗi đối tượng GCM là 2^39 − 256 byte. Đầu vào lớn hơn sẽ phát sinh một ngoại lệ độ dài; hãy phân chia nội dung qua nhiều đối tượng bằng encryptStream().
  • Một thực thể KeyMaterial phải có đúng 32 byte. Quá trình khởi tạo từ chối độ dài sai thay vì cắt ngắn nó.
  • Đường dẫn đọc (verifyUserPassword(), verifyOwnerPassword(), validatePerms()) dùng so sánh thời gian hằng định cho vật liệu mật mã và thất bại đóng (fail closed) trên một dấu hiệu quyền bị hỏng.

Mã hóa AES-256-CBC cho mỗi đối tượng là một lệnh gọi OpenSSL và có độ phức tạp O(n) theo thân đối tượng. Dẫn xuất khóa chạy quy trình Algorithm 2.B lặp một lần cho mỗi thực thể bộ mã hóa; chi phí có giới hạn và không đổi cho mỗi tài liệu. Đường dẫn truyền phát AES-256-GCM phân chia đầu vào thành các khối 16 MiB, giới hạn mức sử dụng heap động khoảng 64 MB bất kể tổng kích thước đầu vào và giữ dưới ngân sách đỉnh 64 MB đã ghi nhận. Mỗi đối tượng GCM thêm 28 byte chi phí phụ (IV 12 byte cộng thẻ 16 byte). Phần cứng AES-NI cải thiện đáng kể thông lượng GCM; nếu không có nó, chỉ thông lượng giảm.

Bề mặt mã hóa này có một mô hình mối đe dọa rõ ràng. Chuẩn hóa SASLprep cộng với dẫn xuất khóa lặp ở bản sửa đổi 6 làm tăng chi phí đoán mật khẩu ngoại tuyến, nhưng mật khẩu yếu vẫn là rủi ro tồn dư chủ đạo. Không có dẫn xuất nào loại bỏ được rủi ro đó. Đường dẫn GCM phát hiện sửa đổi bản mã thông qua xác minh thẻ; đường dẫn CBC mặc định thì không. Trên đường dẫn GCM, một bộ đếm cộng với một tập va chạm ngăn việc tái sử dụng IV, nhất quán với nguyên tắc IV của NIST SP 800-38D §8.1. Tràn bộ đếm bị từ chối thay vì quay vòng. Việc che giấu (redaction) KeyMaterial và thuộc tính #[\SensitiveParameter] trên mật khẩu giúp giảm thiểu rò rỉ khóa qua nhật ký. Vật liệu khóa dẫn xuất được xóa về không sau khi dùng ở những nơi nền tảng cho phép.

Các ranh giới cũng rõ ràng. Core áp dụng mã hóa AES-256 như định nghĩa trong ISO 32000-2:2020 §7.6 và, đối với đường dẫn tùy chọn, ISO/TS 32003:2023 §5.2. Mức bảo vệ thực tế phụ thuộc vào độ mạnh của mật khẩu, việc quản lý khóa, môi trường triển khai và trình đọc tiêu thụ. Các trình đọc tuân thủ tôn trọng các cờ quyền, nhưng mật mã không thực thi chúng. Bước AES-ECB dùng cho giá trị /Perms được ISO 32000-2:2020 §7.6.4.4.10 bắt buộc cho một khối 16 byte đơn lẻ. Đây không phải là chế độ dùng cho mục đích chung. Core cung cấp một bước kiểm tra để xoay khóa trước giới hạn 2^32 lần gọi, nhưng không thực thi theo mặc định; việc xoay khóa là trách nhiệm của phần triển khai.

Quá trình mã hóa và giải mã chạy trong tiến trình; không có byte tài liệu, mật khẩu hay giá trị khóa nào rời khỏi máy chủ qua bề mặt này. Tập va chạm IV của GCM lập khóa theo vân tay khóa không thể đảo ngược, không phải theo các byte khóa. Nếu một bản triển khai đặt khóa phía sau một hệ thống quản lý khóa bên ngoài hoặc một token PKCS#11, thì backend đó chịu trách nhiệm về việc lưu trú; OASIS PKCS#11 v3.1 C_GenerateKey là điểm hợp đồng cho việc sinh khóa thường trú trong token.

Hãy ghi nhật ký tên chính sách và vân tay khóa 8 ký tự, không bao giờ ghi khóa hay mật khẩu. KeyMaterial::__toString()__debugInfo() trả về một chỗ giữ chỗ đã được che giấu. Các ngoại lệ từ bề mặt này bao gồm nhãn thao tác và vân tay, không phải các byte khóa. Số lần gọi GCM là dữ liệu đo từ xa an toàn cho bảng điều khiển xoay khóa.

Mối đe dọaBiện pháp giảm thiểu trong CoreRanh giới tồn dư
Đoán mật khẩu ngoại tuyếnSASLprep cộng dẫn xuất lặp ở bản sửa đổi 6Một mật khẩu yếu vẫn là rủi ro chủ đạo
Sửa đổi bản mãXác minh thẻ GCM (đường dẫn tùy chọn)Đường dẫn CBC chỉ bảo mật
Tái sử dụng IV (GCM)Trường cố định ngẫu nhiên cộng bộ đếm cộng tập va chạm; tràn thì bị từ chối
Văn bản gốc GCM quá dàiKiểm tra độ dài tại 2^39 − 256; hướng dẫn phân chiaBên gọi phải truyền phát đầu vào lớn
Dùng khóa quá mức (GCM)assertWithinSafetyBound() tại 2^32Tùy chọn; không thực thi theo mặc định
Vượt qua cờ quyềnKhông có — các cờ chỉ mang tính khuyến nghịMột trình đọc không tuân thủ sẽ bỏ qua các cờ
Lộ khóa qua nhật kýKeyMaterial được che giấu; #[\SensitiveParameter]Một bên gọi ghi nhật ký exposeKey() sẽ vô hiệu điều này

Core không phải là một mô-đun mật mã được thẩm định FIPS và không được chứng nhận FIPS. CryptoCapabilities::detectFipsMode() là phép dò theo kiểu nỗ lực tối đa, báo cáo trạng thái hoạt động, vắng mặt hoặc không xác định. assertFipsAvailableForProfile() thất bại đóng (fail closed) khi một hồ sơ FIPS được chọn trên máy chủ không chứng minh được có nhà cung cấp FIPS. Bề mặt mã hóa hoạt động ở chế độ tương thích FIPS khi chạy trên một bản dựng OpenSSL của máy chủ đã nạp nhà cung cấp được thẩm định FIPS. Một thế bố trí đã được thẩm định và chứng nhận là vấn đề của bản Enterprise.

Tuyên bốTiêu chuẩnĐiều khoảnBằng chứng
Mỗi IV của GCM là duy nhất cho mỗi lần gọi thông qua một cách dựng tất định kiểu trường-cố-định-cộng-bộ-đếm.NIST SP 800-38D§8.2.1
Nguyên tắc dựng IV ngăn việc tái sử dụng giữa các lần gọi trên cùng một khóa.NIST SP 800-38D§8.1
Giới hạn trên của văn bản gốc cho mỗi đối tượng khớp với giới hạn độ dài cho mỗi lần gọi.NIST SP 800-38D§5.2.1.1
Vòng đời mật mã của khóa và việc xoay khóa là trách nhiệm của phần triển khai.NIST SP 800-57 Part 1 Rev. 5§4
Khóa tệp AES là 256 bit, khớp với độ dài khóa của tiêu chuẩn.FIPS 197§4.2.1
Việc sinh khóa thường trú trong token là điểm tích hợp với kho khóa bên ngoài.OASIS PKCS#11 v3.1C_GenerateKey

ISO 32000-2:2020 §7.6 và ISO/TS 32003:2023 §5.2 là cơ sở quy phạm cho các trình xử lý được ghi nhận ở đây. Văn bản của chúng bị hạn chế giấy phép. Trang này diễn giải lại các tiêu chuẩn đó, trích dẫn điều khoản theo số và không trích nguyên văn bất kỳ điều khoản nào. Bài kiểm thử chuẩn của Algorithm 2.B và đồ gá oracle bên ngoài trong phần đuôi bằng chứng của trang cung cấp bằng chứng thời gian chạy đã được xác minh cho việc dẫn xuất khóa chính xác đến từng byte.

Core cung cấp đường dẫn AES-256-CBC mặc định, đường dẫn AES-256-GCM tùy chọn, một bề mặt khóa cục bộ và cổng chính sách mật mã. Bản Enterprise bổ sung một backend giữ khóa HSM/PKCS#11 và một hồ sơ chính sách mật mã chế độ FIPS phía sau cùng các hợp đồng đó. Giao diện lập trình ứng dụng (API) công khai giống hệt; backend giữ khóa và phần triển khai chính sách thì khác nhau.