💡 Ví dụ hoạt động đầy đủ có sẵn trên GitHub:
qr-sign-password-protected-pdf-python
Giới thiệu
Có một mẫu ba bước mà hầu hết các nhóm thường dùng khi một tài liệu cần ký lại được mã hoá: giải mã, ký bản văn bản thuần, rồi mã hoá lại kết quả. Nó hoạt động. Tuy nhiên, trong vài trăm mili giây, một bản sao có thể đọc được của tài liệu được bảo vệ cố ý sẽ tồn tại trong thư mục tạm, và trong một pipeline được kiểm toán, khoảng thời gian này chính là điểm phát hiện hơn là chữ ký.
Ký một PDF được bảo vệ là một khả năng của GroupDocs.Signature cho Python thông qua .NET, bỏ qua ba bước trên hoàn toàn: mật khẩu mở nguồn tại chỗ, chữ ký được áp dụng, và đầu ra được ghi lại dưới dạng được bảo vệ. Bài viết này so sánh bốn cách xử lý mật khẩu – hai cách hoạt động và hai cách thất bại cố ý – và đề cập đến hợp đồng lỗi đặc thù của binding này.
Tại sao lại quan trọng
Xử lý mật khẩu là nơi các pipeline tài liệu rò rỉ. Không phải qua thư viện ký, mà thường là qua phần khung xung quanh nó: tệp tạm mà lẽ ra phải bị xóa, trình xử lý ngoại lệ nuốt lỗi mật khẩu sai và thử lại mãi mãi, bản sao đã ký được chuyển giao kèm mật khẩu mà người nhận chưa được thông báo.
Cả ba đều có nguyên nhân gốc giống nhau, đó là mật khẩu được xem như một thứ cần loại bỏ chứ không phải là một phần của thao tác. LoadOptions và SaveOptions đưa nó trở lại vào trong thao tác.
Yêu cầu trước
Python 3 và groupdocs-signature-net==26.1, cộng với một PDF có mật khẩu người dùng. Nếu không có giấy phép, thư viện chạy ở chế độ đánh giá, vẫn ký được nhưng sẽ thêm văn bản của riêng nó vào trang.
Cài đặt
pip install groupdocs-signature-net==26.1
Phương pháp 1 – Giữ nguyên mật khẩu gốc
Mặc định, và là cách cần ít mã nhất. Mật khẩu được truyền vào qua LoadOptions, và không truyền SaveOptions nào cả:
load_options = LoadOptions()
load_options.password = password
options = _build_qr_options(qr_text)
with signature.Signature(source_path, load_options) as sign:
result = sign.sign(output_path, options)
return len(result.succeeded)
Việc không có SaveOptions là yếu tố thực hiện công việc ở đây. use_original_password mặc định là True, vì vậy GroupDocs sẽ áp dụng lại mật khẩu nguồn cho đầu ra đã ký. Không có khoảnh khắc nào mà phiên bản không được bảo vệ tồn tại, dù trên đĩa hay ở nơi khác, và len(result.succeeded) báo cáo số lượng chữ ký đã được ghi.
Phương pháp 2 – Đặt lại mật khẩu cho bản sao đã ký
Khi tài liệu đã ký được chuyển cho bên khác, cách hợp lý là cấp cho bản sao một thông tin xác thực riêng và để nguyên nguồn:
save_options = SaveOptions()
save_options.password = new_password
save_options.use_original_password = False
with signature.Signature(source_path, load_options) as sign:
result = sign.sign(output_path, options, save_options)
return len(result.succeeded)
Cả hai dòng SaveOptions đều bắt buộc, và đây là chi tiết cần nhớ: đặt password trong khi để use_original_password ở giá trị mặc định sẽ không tạo ra bất kỳ hiệu ứng nào có thể quan sát được. Cờ use_original_password thắng, đầu ra giữ mật khẩu cũ, và bạn sẽ phát hiện ra khi người nhận báo rằng mật khẩu bạn gửi không hoạt động.
Phương pháp 3 và 4 – Hai trường hợp thất bại
Một tài liệu được mã hoá phản hồi khác nhau khi thiếu mật khẩu và khi mật khẩu sai, và sự khác biệt này đáng để xử lý.
Khi không có LoadOptions nào cả, việc mở sẽ thất bại và không có gì được ghi:
try:
with signature.Signature(source_path) as sign:
sign.sign(output_path, options)
return ""
except RuntimeError as error:
return proxy_error_name(error)
Đoạn mã trên trả về PasswordRequiredException. Cung cấp một mật khẩu không đúng thay vào đó và cùng một đoạn mã sẽ trả về IncorrectPasswordException. Một trường hợp yêu cầu người dùng nhập thông tin xác thực; trường hợp còn lại cho biết thông tin xác thực bạn có đã lỗi thời. Một trình xử lý không phân biệt được chúng sẽ tiếp tục thử lại mật khẩu mà sẽ không bao giờ thành công.
Hợp đồng lỗi, và tại sao mã thông thường lại bị lỗi
Đây là phần có thể khiến bạn mất cả buổi chiều nếu không ai cảnh báo. Binding này phơi bày PasswordRequiredException, IncorrectPasswordException và GroupDocsSignatureException dưới dạng các tên thuần không kế thừa từ BaseException. Viết trình xử lý trực giác:
except IncorrectPasswordException:
...
và Python sẽ ném TypeError: catching classes that do not inherit from BaseException is not allowed. Lỗi gốc đã biến mất, được thay thế bằng một lỗi chỉ trỏ tới dòng except của bạn thay vì tới mật khẩu. Tôi đã viết chính đoạn trình xử lý đó lần đầu, và hai mươi phút tôi dành để đọc TypeError là lý do phần này tồn tại.
Thực tế, những gì nhận được là một RuntimeError với thông điệp bắt đầu bằng Proxy error(<Name>): . Phân tích tiền tố này sẽ khôi phục nguyên nhân:
message = str(error)
marker = "Proxy error("
if not message.startswith(marker):
return ""
start = len(marker)
end = message.find(")", start)
if end < 0:
return ""
return message[start:end]
Hãy rẽ nhánh dựa trên tên trả về thay vì trên nội dung thông điệp, vì nội dung có thể chứa đường dẫn tệp và thay đổi giữa các lần chạy.
Kiểm tra trước khi ký
Có một đường dẫn thứ năm đáng biết, và nó không ghi gì cả. Mở tài liệu với LoadOptions và gọi get_document_info sẽ trả về định dạng, số trang và kích thước trong khi tệp vẫn được mã hoá trên đĩa:
with signature.Signature(source_path, load_options) as sign:
info = sign.get_document_info()
return info.file_type.file_format, info.page_count, info.size
Hai cách sử dụng nó. Khi mật khẩu đến từ một biểu mẫu người dùng, việc này xác thực thông tin xác thực bằng một cuộc gọi rẻ hơn thay vì chờ đến giữa một lô hai trăm tài liệu. Và khi một pipeline không được phép lưu trữ bản văn bản thuần, nó vẫn cho phép pipeline báo cáo những gì nó đang giữ — số trang cho log kiểm toán, kích thước cho hạn ngạch — mà không cần giải mã gì cả.
So sánh các phương pháp: Khi nào nên dùng mỗi phương pháp
| Phương pháp | Thích hợp cho | Ưu điểm chính | Hạn chế |
|---|---|---|---|
| Giữ nguyên mật khẩu gốc | pipeline ký tại chỗ | không cần SaveOptions, không ghi gì dưới dạng rõ ràng |
người nhận cần mật khẩu nguồn |
| Đặt lại mật khẩu khi lưu | chuyển giao cho bên khác | nguồn giữ thông tin xác thực của mình, bản sao có mật khẩu mới | cần hai dòng SaveOptions, dễ nhầm lẫn khi chỉ đặt một |
| Không có mật khẩu (thất bại) | chứng minh hợp đồng trong test | thất bại khi mở, không ghi gì | không phải là đường ký |
| Mật khẩu sai (thất bại) | phân biệt thông tin xác thực lỗi thời | tên ngoại lệ riêng biệt | không phải là đường ký |
Đọc lại có đáng giá việc gọi thêm không?
Có, vì hai lý do. Mở lại tệp đã ký với QrCodeVerifyOptions chứng minh chữ ký vẫn tồn tại sau khi lưu, và vì việc mở lại phải cung cấp mật khẩu, nó cũng chứng minh đầu ra thực sự vẫn được mã hoá. Đếm bằng 0 hầu hết là vấn đề giấy phép chứ không phải lỗi ký – lời gọi sign sẽ ném lỗi khi thực sự thất bại, vì vậy việc im lặng cộng với số đếm 0 chỉ ra một bản dựng không có giấy phép.
Chi phí khi chuyển đổi
Không có gì cấu trúc thay đổi. Nếu mã của bạn đã giải mã ra tệp tạm, thay đổi duy nhất là xóa bước đó, chuyển mật khẩu vào LoadOptions, và loại bỏ lời gọi mã hoá lại ở cuối – thường giảm số dòng. Lời gọi ký tự thân không thay đổi, và đầu ra là một PDF đã ký byte‑for‑byte với cùng mức bảo vệ như khi vào.
Điểm duy nhất cần chú ý là mã dọn dẹp. Một pipeline được xây dựng quanh “giải mã‑ký‑mã hoá lại” thường có khối finally xóa tệp tạm, và khi tệp tạm đã không còn, khối đó sẽ cố gắng xóa một đường dẫn không tồn tại.
Thực hành tốt
- Để
use_original_passwordnguyên trạng trừ khi bạn muốn thay đổi mật khẩu một cách có chủ đích; mặc định là an toàn nhất. - Phân tích tên proxy một lần trong một hàm trợ giúp, và rẽ nhánh dựa trên nó ở mọi nơi khác.
- Xác thực mật khẩu do người dùng cung cấp bằng
get_document_infotrước khi bắt đầu một lô, để một thông tin xác thực sai chỉ tốn một cuộc gọi rẻ thay vì làm gián đoạn một quá trình chạy. - Không bao giờ ghi đầu ra đã ký lên cùng đường dẫn nguồn, để nếu có lỗi, bản gốc vẫn có thể phục hồi.
Kết luận
Mật khẩu không phải là rào cản cần vượt qua trước khi ký – nó là một đối số của thao tác. Mở bằng LoadOptions, quyết định bảo vệ đầu ra bằng SaveOptions, phân tích tên proxy khi có lỗi, và xác minh lại bằng mật khẩu sau khi ký. Mẫu này chạy cả bốn đường cùng một lúc, vì vậy sự khác nhau giữa chúng chỉ cần một lệnh để thấy thay vì một đoạn văn để tin tưởng.