💡 Pełny działający przykład dostępny na GitHubie:
vault-transit-pdf-signing-python
Dlaczego automatyczne podpisywanie nie przechodzi przeglądu zarządzania kluczami
Zespoły ds. zgodności i operacji finansowych podpisują tysiące dokumentów: umowy pożyczkowe, wyciągi, pakiety polityk, listy audytowe. Prawie żaden z nich nie jest podpisany przez osobę klikającą przycisk. Usługa robi to według harmonogramu, a certyfikat dołączony do tych dokumentów nosi nazwę organizacji. To sprawia, że klucz podpisujący jest jednym z najcenniejszych zasobów firmy i zazwyczaj znajduje się w pliku PFX obok aplikacji, która go używa.
Zewnętrzne podpisywanie to funkcja GroupDocs.Signature dla Pythona przez .NET, która przekazuje skrót dokumentu do kodu dostarczonego przez Ciebie zamiast podpisywać go prywatnym kluczem trzymanym przez aplikację. Istnieje, ponieważ układ z PFX nie wytrzymuje kontroli. Plik jest kopią, a kopie rozprzestrzeniają się: do obrazu kontenera, potoku wdrożeniowego, kopii zapasowej, laptopa dewelopera, który debugował problem produkcyjny w zeszłej wiośnie. Każdy, kto posiada jedną z tych kopii i hasło, może podpisywać w Twoim imieniu, dopóki ktoś nie zauważy i nie odwoła certyfikatu.
To, czego potrzebują te zespoły, nie jest inny format pliku. Potrzebują, aby klucz istniał w dokładnie jednym miejscu, z rejestrem audytu przy każdym użyciu, oraz usługi podpisującej, która może poprosić o podpis bez nigdy nie będąc zaufaną z kluczem. Do tego służy menedżer kluczy, taki jak HashiCorp Vault, i pomaga tylko wtedy, gdy biblioteka dokumentów może zostać poinstruowana do delegowania kryptografii.
Dlaczego plik certyfikatu chroniony hasłem nie wystarcza
Instynkt mówi, aby zabezpieczyć PFX: ograniczyć uprawnienia do pliku, przechowywać hasło w menedżerze tajemnic, szyfrować wolumin. Każdy krok pomaga i żaden nie zmienia podstawowej prawdy. W momencie podpisywania prywatny klucz jest ładowany do procesu, więc zrzut pamięci, ucieczka z kontenera lub nieostrożny zapis w logu mogą go ujawnić. Kontrola, której potrzebujesz, to klucz, którego nie da się w ogóle odczytać, a nie klucz trudniejszy do odczytania.
Silnik Transit w Vault zapewnia to. Wykonuje kryptografię jako usługę: wysyłasz wartość, on podpisuje lub szyfruje ją kluczem, który posiada, i zwraca wynik. Klucze tworzone w nim mogą być oznaczone jako nieeksportowalne i nigdy nie przechowuje on tego, co otrzymuje. GroupDocs.Signature pokrywa drugą połowę, ponieważ DigitalSignOptions.custom_sign_hash pozwala podłączyć własny podpisujący do potoku podpisywania PDF.
Konfiguracja: GroupDocs.Signature dla Pythona z Vault Transit
GroupDocs.Signature robi wszystko aż do kryptografii. Otwiera PDF, określa, który zakres bajtów obejmuje podpis, hashuje ten zakres i wywołuje Twój podpisujący ze skrótem. Twój podpisujący przekazuje skrót do Vault, a Vault zwraca wygenerowane bajty. Biblioteka osadza je razem z certyfikatem i zapisuje normalny podpisany PDF.
Potrzebujesz Pythona 3.11 lub 3.13, pakietów groupdocs-signature-net i asn1crypto oraz Dockera dla kontenera Vault w trybie deweloperskim. Licencja GroupDocs jest opcjonalna; bez niej tryb ewaluacji akceptuje dokumenty do 2 stron. Przykładowe repozytorium działa od początku do końca w dwóch poleceniach, a ten sam kod wskazuje na produkcyjny Vault po zmianie dwóch zmiennych środowiskowych.
Budowanie krok po kroku
Krok 1 – Odczyt klucza podpisującego z Vault
Aplikacja pyta Vault o klucz, którego może używać, i otrzymuje numer wersji oraz klucz publiczny. Część prywatna nigdy nie jest częścią odpowiedzi.
key = vault.read_key(KEY_NAME)
if key is None:
# First run: enable Transit and create an RSA-2048 key.
vault.enable_transit()
vault.create_key(KEY_NAME, "rsa-2048")
key = vault.read_key(KEY_NAME)
print(f"Signing key: '{KEY_NAME}', version {key.version}")
Odczyt wersji raz to sens tego kroku. Certyfikat zawiera jeden klucz publiczny, więc certyfikat i każdy podpis muszą pochodzić z tej samej wersji.
Krok 2 – Utworzenie certyfikatu dla klucza, którego nikt nie może wyeksportować
Podpis PDF zawiera certyfikat X.509, aby weryfikator mógł znaleźć klucz publiczny. Przy kluczu nieeksportowalnym certyfikat jest składany wokół części publicznej i podpisywany przez Vault.
tbs_certificate = x509.TbsCertificate({...}) # built around the Vault public key
# Hash locally, then have Vault sign the certificate's own digest.
digest = hashlib.sha256(tbs_certificate.dump()).digest()
signature = vault.sign_hash(key_name, key.version, digest, "sha2-256")
certificate = x509.Certificate({
"tbs_certificate": tbs_certificate,
"signature_algorithm": algorithm,
"signature_value": signature,
})
Certyfikat celowo zaczyna się dzień wcześniej niż teraz. Czytnik PDF porównuje czas podpisu z tą datą początkową i na maszynie dwie godziny na wschód od UTC certyfikat wydany sekundy przed podpisem został zgłoszony przez Adobe Acrobat jako wygasły lub jeszcze nieważny.
Krok 3 – Podpisanie PDF przy użyciu zewnętrznego podpisującego
certificate_object = DigitalVerifyOptions(
certificate_stream=io.BytesIO(certificate)).certificate
digital_signature = DigitalSignature(certificate=certificate_object)
digital_signature.sign_time = datetime.now().astimezone()
options = DigitalSignOptions()
options.signature = digital_signature
options.custom_sign_hash = VaultSigner(vault, KEY_NAME, key.version)
options.hash_algorithm = HashAlgorithm.SHA256
options.reason = "Document signed with HashiCorp Vault Transit"
with gs.Signature(str(input_pdf)) as signature:
signature.sign(str(output_pdf), options)
Dwa wiersze są specyficzne dla Pythona. Powiązanie nie ma typu X.509, więc obiekt certyfikatu pochodzi z DigitalVerifyOptions; podanie ścieżki do DigitalSignOptions zamiast tego powoduje błąd „Specified certificate doesn’t contain private key”. A sign_time jest świadomie strefowo‑czasowy, ponieważ wartość z przesunięciem jest zapisywana w PDF.
Wewnątrz podpisującego skrót jest wysyłany do Vault z prehashed = true i pkcs1v15. Te dwa pola decydują, czy podpis jest ważny, a pierwsze cicho nie działa, gdy brakuje go.
Krok 4 – Weryfikacja przed wysłaniem czegokolwiek z usługi
options = DigitalVerifyOptions(certificate_stream=io.BytesIO(certificate))
with gs.Signature(str(signed_pdf)) as signature:
is_valid = signature.verify(options).is_valid
Podpisywanie i weryfikacja w tym samym uruchomieniu zamienia jedną cichą awarię w natychmiastową. Dla potoku dokumentów jest to brama: plik, który nie przejdzie weryfikacji, nigdy nie trafi do archiwum, kontrahenta ani portalu klienta.
Wpływ na biznes: Co się zmienia w audycie
| Kwestia | PFX na hoście aplikacji | Podpisywanie przez Vault Transit |
|---|---|---|
| Kopie klucza prywatnego | jedna na host, obraz, kopię zapasową i laptopa | jedna, wewnątrz Vault |
| Uprawnienia potrzebne aplikacji podpisującej | pełny dostęp do pliku klucza | odczyt klucza, wywołanie transit/sign |
| Dowód, że dokument został podpisany | logi aplikacji, jeśli ktoś je dodał | jeden wpis audytu Vault na każdy podpis |
| Reakcja na podejrzany wyciek | odwołanie, ponowne wydanie, ponowne wdrożenie wszędzie | rotacja klucza w Vault, jednorazowe ponowne wydanie |
| Kto może podpisywać w Twoim imieniu | każdy z plikiem i hasłem | każdy posiadający ważny token Vault |
Ostatni wiersz jest szczery. Ten projekt przenosi sekret, a nie usuwa go: skradziony token Vault nadal może żądać podpisów. Różnica polega na tym, że token jest krótkotrwały, odwoływalny w jednym miejscu, ograniczony do dwóch operacji i każdy jego użytek jest rejestrowany. To znacznie łatwiejsze do obrony w przeglądzie niż plik, którego kopie nikt nie jest w stanie wyliczyć.
Rzeczywisty przepływ pracy: Umowa pożyczkowa podpisana przez usługę
Platforma pożyczkowa generuje PDF umowy po zatwierdzeniu wniosku, podpisuje go i umieszcza w skarbcu dokumentów klienta. Przed tą zmianą usługa podpisująca trzymała firmowy certyfikat, a podręcznik wdrożeniowy zawierał krok kopiowania PFX na nowe hosty. Po zmianie usługa trzyma token AppRole z dwoma uprawnieniami, a prywatny klucz certyfikatu nigdy nie istnieje poza Vault.
Gdy audytorzy pytają, ile dokumentów zostało podpisanych w ostatnim kwartale i przez kogo, odpowiedź pochodzi z urządzenia audytu Vault, a nie z odtwarzania logów aplikacji. Gdy zespół rotuje klucz, robi to w jednym miejscu i wydaje nowy certyfikat dla nowej wersji.
Co powinienem najpierw zrobić poprawnie?
Obsługa skrótu. Nieprawidłowa nazwa algorytmu wywołuje głośny błąd, a Vault odpowiada
500 crypto/rsa: input must be hashed message, co kosztuje minutę. Brak flagi prehashed nie generuje żadnego błędu: Vault ponownie hashuje skrót, podpis obejmuje niewłaściwą wartość i PDF wygląda na gotowy. Weryfikuj każdy plik, który podpisujesz, aby ta awaria przestała być niewidoczna.
Inne miejsca, w których ten projekt się sprawdza
Hook nie wie, co jest za nim, więc te same cztery kroki działają z modułem bezpieczeństwa sprzętowego, chmurowym KMS lub kartą inteligentną. Przeniosłem tę implementację z jej odpowiednika .NET, spodziewając się, że podpisujący będzie wymagał przebudowy, a jedyną częścią, która naprawdę się zmieniła, było przekazanie certyfikatu; ciało żądania Vault i przypinanie wersji były identyczne. Zespoły w ochronie zdrowia, administracji i ubezpieczeniach sięgają po to z tego samego powodu, co finanse: klucz musi znajdować się w miejscu, które może udowodnić, do czego został użyty.
Rozpoczęcie
- Rozpocznij za darmo: uzyskaj licencję próbną, pełny dostęp do API
- Przeczytaj przewodnik integracji:
podpisywanie PDF przy użyciu klucza Vault Transit w Pythonie - Przeglądaj kod:
vault-transit-pdf-signing-python - Zadawaj pytania implementacyjne na forum społecznościowym