Cách làm việc với PDF Encryption Engine trong Python
Aspose.PDF FOSS cho Python (import aspose_pdf) triển khai bảo vệ mật khẩu PDF ở mức byte thông qua một lớp động cơ nội bộ nhỏ, EncryptionUtils, trong aspose_pdf.engine.encryption. Đây là nơi mà thuật toán mật mã AES-CBC và các thuật toán suy xuất khóa của Trình xử lý Bảo mật Chuẩn PDF (ISO 32000 Các bản sửa đổi 2 đến 6) thực sự tồn tại, độc lập với Document API cấp cao. Hướng dẫn này cài đặt gói và hướng qua ba nhiệm vụ của động cơ: mã hoá và giải mã các byte thô bằng AES-CBC, suy xuất các khóa Trình xử lý Bảo mật Chuẩn cho một mật khẩu (Các bản sửa đổi 2-4), và suy xuất các khóa AES-256 cho tài liệu Bản sửa đổi 5/6.
Hướng dẫn từng bước
Bước 2: Nhập các lớp cần thiết
EncryptionUtils không được xuất lại từ gói cấp cao aspose_pdf — nhập nó trực tiếp từ mô-đun con engine.encryption:
import os
from aspose_pdf.engine.encryption import EncryptionUtilsBước 3: Mã hoá và Giải mã Dữ liệu bằng AES-CBC
encrypt_aes_cbc và decrypt_aes_cbc hoạt động trực tiếp trên bất kỳ khóa 16-, 24- hoặc 32-byte nào (AES-128, AES-192 hoặc AES-256) mà không qua quá trình suy xuất khóa dựa trên mật khẩu. encrypt_aes_cbc áp dụng padding PKCS7 nội bộ, và decrypt_aes_cbc loại bỏ nó.
key = os.urandom(32) # AES-256; 16- and 24-byte keys are also accepted
plaintext = b"Confidential PDF content"
ciphertext = EncryptionUtils.encrypt_aes_cbc(key, plaintext)
decrypted = EncryptionUtils.decrypt_aes_cbc(key, ciphertext)
assert decrypted == plaintext
print("Roundtrip OK, ciphertext length:", len(ciphertext))Bước 4: Suy ra các khóa Trình xử lý Bảo mật Chuẩn cho một mật khẩu (Các phiên bản 2-4)
compute_owner_key_v4 tính giá trị mật khẩu Chủ sở hữu (O) theo Thuật toán 3.3, và compute_user_key_v4 tính giá trị mật khẩu Người dùng (U) và khóa mã hoá tệp theo Thuật toán 3.4/3.5. verify_password_v4 suy lại khóa từ mật khẩu dự đoán để bạn có thể xác nhận nó mở được tài liệu trước khi tin tưởng nó.
owner_password = "owner-secret"
user_password = "user-secret"
key_length = 16 # 128-bit (AES/RC4); Revision 2 uses 5 (40-bit)
revision = 4
file_id = os.urandom(16)
permissions = -4 # standard PDF permissions value
o_value = EncryptionUtils.compute_owner_key_v4(
owner_password, user_password, key_length, revision
)
u_value, encryption_key = EncryptionUtils.compute_user_key_v4(
user_password, o_value, permissions, file_id, key_length, revision
)
verified_key = EncryptionUtils.verify_password_v4(
user_password, u_value, o_value, permissions, file_id, key_length, revision
)
assert verified_key == encryption_key
ciphertext = EncryptionUtils.encrypt_aes_cbc(verified_key, b"Confidential PDF content")
decrypted = EncryptionUtils.decrypt_aes_cbc(verified_key, ciphertext)
assert decrypted == b"Confidential PDF content"o_value và u_value luôn luôn có độ dài 32 byte bất kể key_length. verify_password_v4 trả về None — không phải là một ngoại lệ — khi mật khẩu không khớp.
Bước 5: Suy ra các khóa AES-256 cho tài liệu phiên bản 5/6
Các trình xử lý bảo mật phiên bản 5 và 6 (AES-256) không sử dụng compute_owner_key_v4/compute_user_key_v4 — chúng băm mật khẩu bằng compute_hash_v5, thực hiện “Thuật toán 2.B” của ISO 32000-2. Sử dụng generate_file_id để tạo một định danh tệp mới, và derive_object_key để chuyển khóa mã hoá tệp thành khóa cho một đối tượng gián tiếp cụ thể.
password = b"SecurePassword123"
salt = os.urandom(8)
password_hash = EncryptionUtils.compute_hash_v5(password, salt)
assert len(password_hash) == 32
file_id = EncryptionUtils.generate_file_id()
assert len(file_id) == 16
file_key = os.urandom(32)
object_key = EncryptionUtils.derive_object_key(file_key, 12, 0, use_aes=True)
assert len(object_key) == 16derive_object_key trộn số đối tượng và số thế hệ vào khóa tệp, vì vậy mỗi đối tượng gián tiếp đều có khóa riêng mặc dù khóa tệp được chia sẻ.
Các vấn đề thường gặp và cách khắc phục
encrypt_aes_cbc/decrypt_aes_cbc gây ra “AES key must be 16, 24, or 32 bytes”. Tham số key phải là một khóa AES thực, không phải một chuỗi mật khẩu thô. Tạo một khóa bằng os.urandom(16 | 24 | 32), hoặc suy ra một khóa trước bằng compute_user_key_v4 (Revisions 2-4) hoặc compute_hash_v5 (Bản sửa đổi 5/6).
verify_password_v4 returns None thay vì gây ra. Điều này có nghĩa là mật khẩu không khớp — đây không phải là một lỗi cần bắt, chỉ là kết quả cần kiểm tra. Cũng hãy xác nhận rằng permissions, file_id, key_length, và revision là những giá trị hoàn toàn giống nhau được sử dụng khi tài liệu o_value and u_value được tính toán ban đầu; một sự không khớp ở bất kỳ một trong số chúng sẽ âm thầm tạo ra khóa sai thay vì báo lỗi một cách rõ ràng.
compute_owner_key_v4 với mật khẩu chủ sở hữu trống. Passing "" for owner_password là hành vi được định nghĩa trong đặc tả, không phải lỗi — thuật toán sẽ quay lại sử dụng mật khẩu người dùng, vì vậy mật khẩu chủ và mật khẩu người dùng trở nên tương đương.
Các khóa được tạo cho phiên bản hoặc độ dài khóa không đúng. Phiên bản 2 sử dụng các khóa RC4 40-bit (5-byte), phiên bản 3/4 sử dụng các khóa RC4/AES 128-bit (16-byte), và phiên bản 5/6 sử dụng compute_hash_v5 thay vì compute_owner_key_v4/compute_user_key_v4 hoàn toàn. Việc trộn lẫn chúng không gây ra ngoại lệ — nó chỉ tạo ra một khóa không mở được tài liệu.
compute_hash_v5 đầu ra thay đổi một cách bất ngờ. Tham số thứ ba (user_key) được trộn vào hàm băm. Truyền b"" một cách rõ ràng khi bạn muốn một hàm băm xác thực mật khẩu đơn giản, vì việc bỏ qua nó không giống như truyền một giá trị rỗng trong mọi vị trí gọi.
Câu hỏi thường gặp
Tại sao EncryptionUtils không thể được nhập (import) trực tiếp từ aspose_pdf?
Nó nằm trong subpackage engine vì đó là chi tiết triển khai nội bộ của cơ chế bảo vệ mật khẩu ở mức tài liệu, không phải là phần công cộng API cấp cao. Hãy nhập (import) nó một cách rõ ràng từ aspose_pdf.engine.encryption.
Liệu compute_hash_v5 có giống như một lần gọi SHA-256 duy nhất không?
Không. Nó thực thi Thuật toán 2.B của ISO 32000-2, thực hiện nhiều vòng bằng các thuật toán băm khác nhau được chọn dựa trên các giá trị trung gian — cố ý tốn kém hơn so với một lần gọi băm duy nhất.
Liệu generate_file_id() có trả về cùng các byte mỗi lần không?
Không — nó trả về một giá trị ngẫu nhiên 16 byte mới mỗi lần gọi. Một định danh tệp khác nhau làm thay đổi mọi khóa được suy ra từ nó, ngay cả khi mật khẩu giống nhau.
Tất cả các đối tượng gián tiếp trong PDF đã mã hoá có chia sẻ cùng một khóa không?
Không. derive_object_key kết hợp số đối tượng và số thế hệ với khóa mã hoá tệp, vì vậy mỗi đối tượng gián tiếp nhận được một khóa riêng biệt cho từng đối tượng.
Mã ứng dụng có nên gọi EncryptionUtils trực tiếp để bảo vệ PDF bằng mật khẩu không?
Thường không. EncryptionUtils là động cơ cấp thấp mà các API mật khẩu ở mức tài liệu được xây dựng trên đó. Hầu hết các ứng dụng nên bảo vệ PDF thông qua API ở mức tài liệu và chỉ dùng EncryptionUtils khi cần công cụ tùy chỉnh hoặc kiểm tra pháp y các tệp đã mã hoá.