Jak pracować z silnikiem szyfrowania PDF w Python

Jak pracować z silnikiem szyfrowania PDF w Python

Aspose.PDF FOSS dla Python (import aspose_pdf) implementuje ochronę hasłem PDF na poziomie bajtów poprzez małą wewnętrzną klasę silnika, EncryptionUtils, w aspose_pdf.engine.encryption. To tutaj znajdują się szyfr AES-CBC oraz algorytmy wyprowadzania kluczy PDF Standard Security Handler (ISO 32000 rewizje 2-do-6), niezależnie od wysokopoziomowego Document API. Ten przewodnik instaluje pakiet i przeprowadza przez trzy zadania silnika: szyfrowanie i odszyfrowywanie surowych bajtów przy użyciu AES-CBC, wyprowadzanie kluczy Standard Security Handler dla hasła (rewizje 2-4) oraz wyprowadzanie kluczy AES-256 dla dokumentów rewizja 5/6.

Przewodnik krok po kroku

Krok 2: Importuj wymagane klasy

EncryptionUtils nie jest ponownie eksportowane z pakietu najwyższego poziomu aspose_pdf — importuj je bezpośrednio z podmodułu engine.encryption:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

Krok 3: Szyfruj i odszyfruj dane przy użyciu AES-CBC

encrypt_aes_cbc i decrypt_aes_cbc działają bezpośrednio na dowolnym kluczu 16-, 24- lub 32-bajtowym (AES-128, AES-192 lub AES-256) bez przechodzenia przez wyprowadzanie klucza oparte na haśle. encrypt_aes_cbc stosuje wewnętrznie wypełnienie PKCS7, a decrypt_aes_cbc je usuwa.

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))

Krok 4: Wyprowadź standardowe klucze obsługi zabezpieczeń dla hasła (wersje 2-4)

compute_owner_key_v4 oblicza wartość hasła właściciela (O) zgodnie z Algorytmem 3.3, a compute_user_key_v4 oblicza wartość hasła użytkownika (U) oraz klucz szyfrowania pliku zgodnie z Algorytmem 3.4/3.5. verify_password_v4 ponownie wyprowadza klucz z proponowanego hasła, abyś mógł potwierdzić, że otwiera dokument, zanim mu zaufasz.

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 i u_value mają zawsze 32 bajty, niezależnie od key_length. verify_password_v4 zwraca None — nie jest to wyjątek — gdy hasło nie pasuje.


Krok 5: Wyprowadź klucze AES-256 dla dokumentów wersji 5/6

Obsługa zabezpieczeń wersji 5 i 6 (AES-256) nie używa compute_owner_key_v4/compute_user_key_v4 — hashuje hasło przy użyciu compute_hash_v5, które implementuje „Algorithm 2.B” standardu ISO 32000-2. Użyj generate_file_id jako nowego identyfikatora pliku oraz derive_object_key, aby przekształcić klucz szyfrowania pliku w klucz dla jednego konkretnego obiektu pośredniego.

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) == 16

derive_object_key miesza numer obiektu i numer generacji w kluczu pliku, dzięki czemu każdy obiekt pośredni otrzymuje własny klucz, mimo że klucz pliku jest współdzielony.


Typowe problemy i rozwiązania

encrypt_aes_cbc/decrypt_aes_cbc rzuca “AES key must be 16, 24, or 32 bytes”. Argument klucza musi być prawdziwym kluczem AES, a nie surowym ciągiem hasła. Wygeneruj go przy pomocy os.urandom(16 | 24 | 32), albo najpierw wyprowadź go przy pomocy compute_user_key_v4 (Revisions 2-4) lub compute_hash_v5 (Rewizje 5/6).

verify_password_v4 returns None zamiast rzucać. To oznacza, że hasło nie pasuje — nie jest to warunek błędu do przechwycenia, a jedynie wynik do sprawdzenia. Również potwierdź, że permissions, file_id, key_length, i revision są dokładnie takimi samymi wartościami użytymi, gdy dokument o_value and u_value zostały pierwotnie obliczone; niezgodność w którejkolwiek z nich cicho generuje niewłaściwy klucz zamiast wywołać głośny błąd.

compute_owner_key_v4 z pustym hasłem właściciela. Passing "" for owner_password jest zachowaniem określonym w specyfikacji, a nie błędem — algorytm przechodzi na użycie hasła użytkownika, więc hasła właściciela i użytkownika stają się równoważne.

Klucze wyprowadzone dla niewłaściwej wersji lub długości klucza. Wersja 2 używa 40-bitowych (5-bajtowych) kluczy RC4, wersje 3/4 używają 128-bitowych (16-bajtowych) kluczy RC4/AES, a wersje 5/6 używają compute_hash_v5 zamiast compute_owner_key_v4/compute_user_key_v4 całkowicie. Mieszanie ich nie generuje wyjątku — po prostu tworzy klucz, który nie otworzy dokumentu.

compute_hash_v5 wyjście zmienia się nieoczekiwanie. Trzeci argument (user_key) jest mieszany z hashem. Przekaż b"" jawnie, gdy chcesz uzyskać czysty hash weryfikacji hasła, ponieważ pominięcie go nie jest tym samym co przekazanie pustej wartości w każdym miejscu wywołania.


Najczęściej zadawane pytania

Dlaczego EncryptionUtils nie jest importowalne bezpośrednio z aspose_pdf?

Znajduje się w podpakiecie engine, ponieważ jest wewnętrzną szczegółowością implementacji ochrony hasłem na poziomie dokumentu, a nie częścią wysokopoziomowego publicznego interfejsu API. Importuj go explicite z aspose_pdf.engine.encryption.

Czy compute_hash_v5 jest tym samym co pojedyncze wywołanie SHA-256?

Nie. Implementuje Algorytm 2.B z ISO 32000-2, który wykonuje wiele rund przy użyciu różnych algorytmów skrótu wybranych na podstawie wartości pośrednich — celowo droższy niż pojedyncze wywołanie skrótu.

Czy generate_file_id() zwraca te same bajty za każdym razem?

Nie — zwraca nową, losową wartość 16-bajtową przy każdym wywołaniu. Inny identyfikator pliku zmienia każdy klucz pochodny, nawet gdy hasło jest identyczne.

Czy wszystkie pośrednie obiekty w zaszyfrowanym PDF-ie dzielą jeden klucz?

Nie. derive_object_key łączy numer obiektu i numer generacji z kluczem szyfrowania pliku, więc każdy pośredni obiekt otrzymuje odrębny, specyficzny dla obiektu klucz.

Czy kod aplikacji powinien wywoływać EncryptionUtils bezpośrednio, aby zabezpieczyć PDF hasłem?

Zazwyczaj nie. EncryptionUtils jest niskopoziomowym silnikiem, na którym opierają się API haseł na poziomie dokumentu. Większość aplikacji powinna chronić PDF-y poprzez API na poziomie dokumentu i sięgać po EncryptionUtils tylko w przypadku własnych narzędzi lub forensycznej analizy zaszyfrowanych plików.


Zobacz także

 Polski