💡 Ví dụ hoạt động đầy đủ có trên GitHub:
pdf-signing-certificate-checks-python
Giới thiệu
Một dịch vụ ký các tệp PDF đã tải lên mỗi đêm. Một buổi sáng, chứng chỉ mà nó sử dụng đã vượt quá ngày hết hạn, và dường như không có gì thay đổi: công việc vẫn chạy, các tệp được ghi, nhật ký trông bình thường. Vài tuần sau, ai đó mở một trong những tài liệu đó trong Acrobat và thấy một biểu ngữ cảnh báo, vì một chữ ký được tạo bằng chứng chỉ đã hết hạn không phải là một chữ ký yếu hơn – nó là một chữ ký mà các bộ kiểm tra báo là không hợp lệ. Các tài liệu trông như đã được phê duyệt có giá trị kém hơn các tài liệu chưa ký, vì mọi người đã tin tưởng chúng.
Sự từ chối này có một tên gọi. Kiểm tra tính hợp lệ của chứng chỉ là một hành vi của GroupDocs.Signature cho Python mà từ chối ký khi thời gian hiệu lực của chứng chỉ đã hết, hoặc chưa bắt đầu. Nó xuất hiện trong phiên bản 26.9 cùng với hai thay đổi có cùng hình dạng: SHA-256 trở thành thuật toán băm mặc định cho các chữ ký PDF, và SignatureSettings.log_level bắt đầu lọc thay vì bị im lặng bỏ qua. Mỗi thay đổi đưa một kết quả trước đây diễn ra âm thầm lên trước mắt bạn.
Bài viết này so sánh ba kiểm soát đó khi chúng hoạt động từ Python thông qua .NET – mỗi cái thay đổi gì trong đầu ra, khi nào nên dùng, và hai chi tiết của binding đã khiến tôi mất một buổi chiều. Mọi kết quả được trích dẫn đều được lấy từ việc chạy mẫu trên một tệp PDF một trang.
Tại sao điều này quan trọng hơn một ghi chú phiên bản
Ba thay đổi này có một đặc điểm đáng đặt tên: tất cả chúng chuyển một lỗi mà bạn sẽ phát hiện sau này thành một lỗi mà bạn phát hiện ngay bây giờ.
- Chứng chỉ hết hạn: lời gọi ký thất bại ngay khi ai đó có thể gia hạn chứng chỉ, thay vì tạo ra các tài liệu không hợp lệ sau khi phân phối.
- Mặc định băm: các chữ ký mới sử dụng SHA-256 mà không cần ai nhớ yêu cầu, vì vậy tùy chọn yếu hơn đòi hỏi một quyết định thay vì sự bất cẩn.
- Mức nhật ký: một dịch vụ cấu hình chỉ nhận cảnh báo giờ chỉ nhận cảnh báo, khiến các cảnh báo dễ đọc, và do đó chúng được đọc.
Điều cuối cùng không chỉ là thẩm mỹ. Giá trị thực của cảnh báo chứng chỉ hết hạn là ai đó sẽ nhìn thấy nó, trong khi một cảnh báo bị chôn sâu trong mười tin trace cho mỗi lần ký là một cảnh báo không ai nhìn thấy.
Yêu cầu trước
Trước khi bắt đầu, hãy chắc chắn rằng bạn có:
- Python 3.9 trở lên trên một trình thông dịch 64‑bit – gói này đi kèm một runtime .NET được đóng gói và không có bánh xe 32‑bit.
- GroupDocs.Signature cho Python thông qua .NET 26.10.0, với một giấy phép tạm thời miễn phí nếu bạn muốn gỡ bỏ các giới hạn đánh giá.
- Một tệp PDF để ký, và gói
cryptographynếu bạn muốn tạo các chứng chỉ thử nghiệm như mẫu làm.
Cài đặt
pip install groupdocs-signature-net cryptography
Kiểm soát 1 – Thuật toán băm được ghi vào chữ ký
hash_algorithm trên DigitalSignOptions chọn thuật toán băm. Mặc định kể từ 26.9 là SHA-256, ở định dạng adbe.pkcs7.detached mà các bộ kiểm tra hiện tại mong đợi; trước đó, các chữ ký mới sử dụng SHA-1.
with signature.Signature(source_path) as sign:
options = DigitalSignOptions()
options.certificate_stream = io.BytesIO(pfx)
options.password = PASSWORD
options.hash_algorithm = HashAlgorithm.SHA512
options.reason = "Approved"
result = sign.sign(output_path, options)
return len(result.succeeded)
Hai chi tiết đáng chú ý:
- Chứng chỉ được truyền qua
certificate_streamdưới dạngio.BytesIOthay vì đường dẫn tệp, cho phép một PKCS#12 được tạo trong bộ nhớ tới thư viện mà không bao giờ được ghi ra đĩa – mẫu dựa vào cách này để không phải ship bất kỳ khóa riêng nào. HashAlgorithmcung cấp các giá trịAUTO,SHA1,SHA256,SHA384vàSHA512; nếu bạn thêm dấu thời gian, nó sẽ sử dụng cùng một thuật toán băm mà chữ ký đã dùng.
Trong thực tế, đây là kiểm soát bạn ít chạm tới nhất. Mặc định đã là câu trả lời đúng, SHA384 và SHA512 tồn tại cho những trường hợp chính sách ký yêu cầu chúng, và SHA1 là một cài đặt tương thích cho các bộ kiểm tra mà bạn không thể thay đổi.
Kiểm soát 2 – Chứng chỉ đã hết hạn có ngăn bạn không
Nếu không có bất kỳ ghi đè nào, việc ký bằng một chứng chỉ mà thời gian hiệu lực đã kết thúc – hoặc chưa bắt đầu – sẽ ném ra GroupDocsSignatureException và không ghi gì cả.
try:
sign.sign(output_path, options)
return True
except signature.GroupDocsSignatureException as error:
print(f"Rejected: {str(error).splitlines()[0]}")
return False
Thông điệp sẽ nêu tên chứng chỉ, ngày hết hạn, dấu vân tay và thuộc tính cho phép, đủ để một ứng dụng thông báo cho người vận hành biết cần gia hạn gì. Việc chỉ lấy dòng đầu tiên quan trọng trong Python: phần còn lại của văn bản ngoại lệ chứa stack trace của .NET phía sau binding, và điều đó không nên hiển thị cho người dùng.
Khi bạn thực sự cần ký dù sao – ví dụ kiểm tra với một chứng chỉ đã lưu trữ, hoặc một lô cần chạy tối đêm trong khi quá trình gia hạn đang diễn ra – bạn có thể ghi đè trên mỗi lời gọi:
settings = signature.SignatureSettings(ConsoleLogger())
settings.log_level = LogLevel.WARNING | LogLevel.ERROR
with signature.Signature(source_path, settings=settings) as sign:
options.allow_expired = True
result = sign.sign(output_path, options)
allow_not_yet_valid có cùng hình dạng cho một chứng chỉ được phát hành cho ngày sau, và hai cờ này độc lập: cho phép một chứng chỉ đã hết hạn không đồng nghĩa với việc cho phép một chứng chỉ chưa tới ngày hiệu lực. Một chứng chỉ sớm thường chỉ ra đồng hồ máy sai chứ không phải chứng chỉ có vấn đề, và đồng hồ sai làm cho mọi chữ ký mà máy tạo ra đều đáng ngờ, vì vậy hãy kiểm tra đồng hồ trước khi ghi đè bất kỳ thứ gì.
Cả hai ghi đè đều phát ra một cảnh báo thay vì im lặng, và đây là phần liên quan tới kiểm soát thứ ba.
Kiểm soát 3 – Ai sẽ biết được
SignatureSettings.log_level là một giá trị cờ. Mẫu ký cùng một tài liệu ba lần, với LogLevel.NONE, LogLevel.WARNING | LogLevel.ERROR và LogLevel.ALL, đếm số tin nhắn nhận được:
logger = CollectingLogger()
settings = signature.SignatureSettings(logger)
settings.log_level = level
with signature.Signature(source_path, settings=settings) as sign:
options.allow_expired = True
sign.sign(output_path, options)
Kết quả sẽ là: không gì cả, sau đó một cảnh báo, rồi cảnh báo đó cộng thêm mười tin trace. Trước 26.9, ba hàng này sẽ giống hệt nhau, vì mức độ được chấp nhận và bị bỏ qua – điều này đáng biết nếu bạn từng đặt một mức, không thấy thay đổi và kết luận mình đã đọc sai mã.
Hai chi tiết của binding đã khiến tôi mất một buổi chiều, vì vậy tôi sẽ nêu chúng một cách rõ ràng:
SignatureSettings.loggerlà read‑only, vì vậy logger phải được truyền vào qua constructor; việc gán lại sẽ némAttributeError.log_levelđược đặt bình thường sau đó.- Một logger tùy chỉnh không được kế thừa từ
groupdocs.signature.logging.ILogger– lớp cơ sở này bọc một đối tượng native mà constructor của nó cần một handle do thư viện sở hữu, nên việc kế thừa sẽ némTypeError. Thay vào đó, binding chấp nhận bất kỳ đối tượng thuần nào cung cấp ba phương thức sau:
class StdlibLogger:
def error(self, message, exception=None):
logging.getLogger("groupdocs").error(message, exc_info=exception)
def warning(self, message, exception=None):
logging.getLogger("groupdocs").warning(message)
def trace(self, message):
logging.getLogger("groupdocs").debug(message)
Hãy để error và warning có tham số tùy chọn exception. Thư viện không phải lúc nào cũng truyền vào một ngoại lệ, và một logger yêu cầu tham số này sẽ bị lỗi khi nhận các tin nhắn không có nó.
So sánh ba kiểm soát: Khi nào dùng mỗi cái
| Kiểm soát | Thích hợp cho | Ưu điểm chính | Hạn chế |
|---|---|---|---|
hash_algorithm |
đáp ứng chính sách quy định thuật toán băm | một lần gán; kích thước đầu ra không thay đổi | vô nghĩa nếu chứng chỉ tự nó không đáng tin |
| kiểm tra tính hợp lệ và ghi đè | bất kỳ trường hợp ký cho người khác | lỗi xuất hiện ở nơi có thể sửa | ghi đè tạo ra tệp, nhưng không phải tệp đáng tin |
log_level |
dịch vụ có nhật ký đã quá tải | mười một tin thành một | chỉ lọc nhật ký, không bao giờ lọc ngoại lệ |
Chúng không phải là các lựa chọn thay thế – một lời gọi ký duy nhất sẽ dùng cả ba. Thứ tự suy nghĩ là: kiểm tra tính hợp lệ quyết định liệu tệp có tồn tại hay không, thuật toán băm quyết định nội dung bên trong, và mức nhật ký quyết định ai biết được.
Mức nhật ký có thay đổi các ngoại lệ tôi nhận được không?
Không. Nó chỉ quyết định tin nhắn nào tới logger của bạn và không gì hơn. Một chứng chỉ hết hạn vẫn ném GroupDocsSignatureException ngay cả khi LogLevel.NONE, và allow_expired vẫn ký được khi LogLevel.ALL; giá trị trả về và ngoại lệ đều giống nhau ở mọi mức. Điều thay đổi duy nhất là liệu cảnh báo giải thích chữ ký đáng ngờ có được người đọc đọc hay không.
Xác minh đã di chuyển cùng hướng
Đáng đề cập vì đây là nửa còn lại của cùng một bản phát hành. verify với một DigitalVerifyOptions rỗng hiện nay kiểm tra mọi chữ ký số PDF một cách mật mã, vì vậy một tài liệu bị thay đổi sau khi ký sẽ trở lại không hợp lệ thay vì chỉ “không giải thích được”:
with signature.Signature(signed_path) as sign:
result = sign.verify(DigitalVerifyOptions())
return result.is_valid
Chỉ hai dòng, và đáng bổ sung vào bất kỳ pipeline nào ký rồi lưu. Lưu ý rằng một giá trị True không hứa hẹn gì hơn: nó chỉ nói chữ ký khớp với tài liệu, không phải nhà phát hành được tin cậy. Các chứng chỉ tự ký trong mẫu được xác minh ở đây nhưng vẫn bị từ chối bởi trình đọc PDF, trả lời câu hỏi về độ tin cậy một cách riêng biệt.
Thực hành tốt và mẹo
- Giữ việc từ chối làm mặc định trong mọi thứ ký thay mặt người dùng, và chỉ ghi đè trên mỗi lời gọi thay vì toàn cục. Ngoại lệ là rẻ, nhưng một lô chữ ký không hợp lệ thì không.
- Ghi lại nội dung cảnh báo, không chỉ số đếm. Nó nêu tên chứng chỉ và ngày, là phần duy nhất mà người vận hành có thể hành động.
- Kiểm tra đồng hồ trước khi cho phép chứng chỉ chưa tới ngày hiệu lực. Thông thường chứng chỉ đúng, máy sai, và điều này ảnh hưởng tới hơn một lời gọi ký.
- Không để trace trong môi trường production. Khoảng mười trace cho mỗi lần ký nhanh chóng cộng dồn; bật chúng khi chẩn đoán và tắt lại sau.
- Xác minh sau khi ký trong bất kỳ pipeline nào, bây giờ việc kiểm tra đã là mật mã, vì vậy đầu ra hỏng sẽ bị bắt trước khi người nhận phát hiện.
Kết luận
Ba kiểm soát, một lời gọi ký, và cùng một ý tưởng thiết kế: kết quả rủi ro giờ cần một quyết định, còn kết quả an toàn không cần gì thêm. Giữ kiểm tra tính hợp lệ, coi allow_expired như một ngoại lệ per‑call mà bạn ghi log, để nguyên thuật toán băm trừ khi chính sách yêu cầu khác, và đặt mức nhật ký sao cho các cảnh báo dễ đọc.
Chạy mẫu trên một PDF của bạn mất khoảng một phút và in ra chính xác những gì mỗi kiểm soát đã thay đổi – sáu tệp đã ký, một lần từ chối có chủ đích, và ba hàng đếm tin nhắn không còn giống nhau nữa.