Skip to content

Mã hóa nâng cao

Pro — Commercial License Required
Chi tiết mã hóa nâng cao yêu cầu package Pro.

Trang này mô tả implement mã hóa nội bộ trong TCPDF-Next Pro. Nó bao gồm handler AES-256 AESV3, thuật toán key derivation, chuẩn hóa mật khẩu và xử lý tham số nhạy cảm.

AES-256 với Handler AESV3

TCPDF-Next Pro implement ISO 32000-2 (PDF 2.0) Standard Security Handler revision 6, yêu cầu AES-256-CBC cho mọi mã hóa stream và string. Handler được nhận dạng bởi /V 5/R 6 trong encryption dictionary.

php
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;

$encryptor = new Aes256Encryptor(
    ownerPassword: 'Str0ng!OwnerP@ss',
    userPassword:  'reader2026',
);

Tại sao không có RC4 hoặc AES-128

TCPDF-Next Pro loại bỏ RC4 (40-bit và 128-bit) và AES-128 có chủ đích:

Thuật toánLý do loại bỏ
RC4-40Bị phá từ 1995; tấn công trivially
RC4-128Bias trong keystream; cấm bởi PDF 2.0
AES-128Bị thay thế bởi AES-256 trong revision 6; không forward-compatible

PDF 2.0 (ISO 32000-2:2020) yêu cầu AESV3 cho document mới. Hỗ trợ thuật toán yếu hơn sẽ làm suy yếu bảo mật và vi phạm đặc tả.

Algorithm 2.B: Key Derivation

File encryption key được tạo từ mật khẩu dùng Algorithm 2.B (ISO 32000-2, clause 7.6.4.3.4). Đây là quá trình lặp chain SHA-256, SHA-384 và SHA-512:

function computeHash(password, salt, userKey = ''):
    K = SHA-256(password || salt || userKey)

    round = 0
    lastByte = 0

    while round < 64 OR lastByte > round - 32:
        K1 = (password || K || userKey) lặp 64 lần
        E  = AES-128-CBC(key = K[0..15], iv = K[16..31], data = K1)

        mod3 = (tổng mọi byte trong E) mod 3
        if   mod3 == 0: K = SHA-256(E)
        elif mod3 == 1: K = SHA-384(E)
        else:           K = SHA-512(E)

        lastByte = E[len(E) - 1]
        round += 1

    return K[0..31]   // file encryption key 32-byte

Hashing lặp này làm tấn công brute-force tốn kém về mặt tính toán trong khi vẫn đủ nhanh cho sử dụng hợp pháp.

Thành phần Key trong Encryption Dictionary

EntryĐộ dàiMục đích
/O48 byteXác thực owner password (hash + validation salt)
/U48 byteXác thực user password (hash + validation salt)
/OE32 byteFile encryption key mã hóa bởi owner
/UE32 byteFile encryption key mã hóa bởi user
/Perms16 byteCờ permission mã hóa AES-256

Chuẩn hóa mật khẩu SASLprep

Trước mọi thao tác mật mã, mật khẩu được chuẩn hóa dùng SASLprep (RFC 4013), là profile của stringprep (RFC 3454). Điều này đảm bảo xử lý mật khẩu nhất quán bất kể dạng chuẩn hóa Unicode của hệ điều hành hoặc phương thức nhập.

php
use Yeeefang\TcpdfNext\Pro\Security\SaslPrep;

$normalized = SaslPrep::prepare('P\u{00E4}ssw\u{00F6}rd');
// Chuẩn hóa dạng composed/decomposed, ánh xạ ký tự nhất định,
// và từ chối codepoint bị cấm.

SASLprep làm gì

  1. Map -- Ký tự commonly mapped-to-nothing (vd: soft hyphen U+00AD) bị loại bỏ.
  2. Normalize -- Chuỗi được chuyển sang Unicode NFC (Canonical Decomposition rồi Canonical Composition).
  3. Prohibit -- Ký tự từ RFC 3454 Table C.1.2 đến C.9 bị từ chối (ký tự điều khiển, private use, surrogate, non-character, v.v.).
  4. Kiểm tra Bidirectional -- Chuỗi có cả ký tự trái-sang-phải và phải-sang-trái được xác thực theo RFC 3454 clause 6.

Điều này nghĩa là người dùng nhập U+00FC (LATIN SMALL LETTER U WITH DIAERESIS) và người khác nhập U+0075 U+0308 (LATIN SMALL LETTER U + COMBINING DIAERESIS) sẽ tạo cùng encryption key.

Mã hóa Permission

Permission được lưu trong entry /Perms dạng block mã hóa AES-256-ECB 16 byte. Layout plaintext:

Byte 0-3:   Cờ permission (little-endian int32)
Byte 4-7:   0xFFFFFFFF
Byte 8:     'T' nếu EncryptMetadata là true, 'F' ngược lại
Byte 9-11:  'adb'
Byte 12-15: Padding ngẫu nhiên

Cờ permission theo cùng layout bit định nghĩa trong ISO 32000-2 Table 22.

Xử lý tham số nhạy cảm

Mọi method nhận mật khẩu được đánh dấu attribute #[\SensitiveParameter] của PHP 8.2. Điều này ngăn mật khẩu xuất hiện trong stack trace, debug output và log lỗi:

php
public function setOwnerPassword(
    #[\SensitiveParameter] string $password,
): self {
    $this->ownerPassword = SaslPrep::prepare($password);
    return $this;
}

Nếu exception xảy ra, stack trace sẽ hiện Object(SensitiveParameterValue) thay vì chuỗi mật khẩu thực.

Ví dụ đầy đủ

php
use Yeeefang\TcpdfNext\Core\Document;
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
use Yeeefang\TcpdfNext\Pro\Security\Permissions;

$pdf = Document::create()
    ->setTitle('Confidential Report')
    ->addPage()
    ->setFont('Helvetica', size: 12)
    ->multiCell(0, 6, 'This document is protected with AES-256 encryption.');

$encryptor = new Aes256Encryptor(
    ownerPassword: 'Str0ng!OwnerP@ss',
    userPassword:  'reader2026',
    permissions:   new Permissions(
        print:            true,
        printHighQuality: false,
        copy:             false,
        modify:           false,
        annotate:         true,
        fillForms:        true,
        extractForAccess: true,
        assemble:         false,
    ),
);

$pdf->encrypt($encryptor)
    ->save(__DIR__ . '/encrypted.pdf');

Phân phối theo giấy phép LGPL-3.0-or-later.