Mã hóa nâng cao
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 và /R 6 trong encryption dictionary.
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án | Lý do loại bỏ |
|---|---|
| RC4-40 | Bị phá từ 1995; tấn công trivially |
| RC4-128 | Bias trong keystream; cấm bởi PDF 2.0 |
| AES-128 | Bị 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-byteHashing 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ài | Mục đích |
|---|---|---|
/O | 48 byte | Xác thực owner password (hash + validation salt) |
/U | 48 byte | Xác thực user password (hash + validation salt) |
/OE | 32 byte | File encryption key mã hóa bởi owner |
/UE | 32 byte | File encryption key mã hóa bởi user |
/Perms | 16 byte | Cờ 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.
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ì
- Map -- Ký tự commonly mapped-to-nothing (vd: soft hyphen
U+00AD) bị loại bỏ. - Normalize -- Chuỗi được chuyển sang Unicode NFC (Canonical Decomposition rồi Canonical Composition).
- 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.).
- 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ênCờ 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:
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 đủ
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');