Comment travailler avec le moteur de chiffrement PDF dans Python

Comment travailler avec le moteur de chiffrement PDF dans Python

Aspose.PDF FOSS pour Python (import aspose_pdf) implémente la protection par mot de passe PDF au niveau des octets via une petite classe interne de moteur, EncryptionUtils, dans aspose_pdf.engine.encryption. C’est ici que le chiffre AES-CBC et les algorithmes de dérivation de clé du Gestionnaire de sécurité standard PDF (ISO 32000 Révisions 2 à 6) résident réellement, indépendamment du Document de haut niveau API. Ce guide installe le paquet et décrit trois tâches du moteur: chiffrer et déchiffrer les octets bruts avec AES-CBC, dériver les clés du Gestionnaire de sécurité standard pour un mot de passe (Révisions 2-4), et dériver les clés AES-256 pour les documents des Révisions 5/6.

Guide étape par étape

Étape 2: Importer les classes requises

EncryptionUtils n’est pas réexporté depuis le paquet de niveau supérieur aspose_pdf — importez-le directement depuis le sous-module engine.encryption:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

Étape 3: Chiffrer et déchiffrer les données avec AES-CBC

encrypt_aes_cbc et decrypt_aes_cbc fonctionnent directement sur toute clé de 16, 24 ou 32 octets (AES-128, AES-192 ou AES-256) sans passer par la dérivation de clé basée sur un mot de passe. encrypt_aes_cbc applique le remplissage PKCS7 en interne, et decrypt_aes_cbc le supprime.

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

Étape 4 : Dériver les clés du gestionnaire de sécurité standard pour un mot de passe (Révisions 2-4)

compute_owner_key_v4 calcule la valeur du mot de passe propriétaire (O) selon l’Algorithme 3.3, et compute_user_key_v4 calcule la valeur du mot de passe utilisateur (U) ainsi que la clé de chiffrement du fichier selon les Algorithmes 3.4/3.5. verify_password_v4 re-déduit la clé à partir d’un mot de passe candidat afin que vous puissiez confirmer qu’il ouvre le document avant de lui faire confiance.

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 et u_value font toujours 32 octets, quel que soit key_length. verify_password_v4 renvoie None — pas une exception — lorsque le mot de passe ne correspond pas.


Étape 5 : Dériver les clés AES-256 pour les documents des révisions 5/6

Les gestionnaires de sécurité des révisions 5 et 6 (AES-256) n’utilisent pas compute_owner_key_v4/compute_user_key_v4 — ils hachent le mot de passe avec compute_hash_v5, qui implémente l’“Algorithme 2.B” d’ISO 32000-2. Utilisez generate_file_id pour un identifiant de fichier frais, et derive_object_key pour transformer une clé de chiffrement du fichier en une clé pour un objet indirect spécifique.

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 mélange le numéro d’objet et le numéro de génération dans la clé du fichier, de sorte que chaque objet indirect obtienne sa propre clé même si la clé du fichier est partagée.


Problèmes courants et solutions

encrypt_aes_cbc/decrypt_aes_cbc lève “AES key must be 16, 24, or 32 bytes”. L’argument key doit être une vraie clé AES, pas une chaîne de mot de passe brute. Générez-en une avec os.urandom(16 | 24 | 32), ou dérivez-en d’abord une avec compute_user_key_v4 (Révisions 2-4) ou compute_hash_v5 (Révisions 5/6).

verify_password_v4 returns None au lieu de lever. Cela signifie que le mot de passe ne correspondait pas — ce n’est pas une condition d’erreur à intercepter, seulement un résultat à vérifier. Confirmez également que permissions, file_id, key_length, et revision sont exactement les mêmes valeurs utilisées lorsque le document o_value and u_value ont été initialement calculées ; une discordance sur l’une d’elles produit silencieusement la mauvaise clé au lieu d’échouer bruyamment.

compute_owner_key_v4 avec un mot de passe propriétaire vide. Passing "" for owner_password est un comportement défini par la spécification, pas un bug — l’algorithme revient à utiliser le mot de passe utilisateur, de sorte que les mots de passe propriétaire et utilisateur deviennent équivalents.

Clés dérivées pour la mauvaise révision ou longueur de clé. La révision 2 utilise des clés RC4 de 40 bits (5 octets), les révisions 3/4 utilisent des clés RC4/AES de 128 bits (16 octets), et les révisions 5/6 utilisent compute_hash_v5 au lieu de compute_owner_key_v4/compute_user_key_v4 entièrement. Mélanger ceux-ci ne lève pas d’exception — cela produit simplement une clé qui n’ouvrira pas le document.

compute_hash_v5 la sortie change de façon inattendue. Le troisième argument (user_key) est mélangé dans le hachage. Passez b"" explicitement lorsque vous voulez un hachage de validation de mot de passe simple, car l’omettre n’est pas équivalent à passer une valeur vide dans chaque site d’appel.


Foire aux questions

Pourquoi EncryptionUtils n’est-il pas importable directement depuis aspose_pdf?

Il se trouve dans le sous-package engine parce qu’il s’agit d’un détail d’implémentation interne de la protection par mot de passe au niveau du document, et non d’une partie de l’interface publique API de haut niveau. Importez-le explicitement depuis aspose_pdf.engine.encryption.

Est-ce que compute_hash_v5 est identique à un appel unique de SHA-256?

Non. Il implémente l’Algorithme 2.B de la norme ISO 32000-2, qui effectue plusieurs tours en utilisant différents algorithmes de hachage sélectionnés selon des valeurs intermédiaires — délibérément plus coûteux qu’un appel de hachage unique.

Est-ce que generate_file_id() renvoie les mêmes octets à chaque fois?

Non — il renvoie une nouvelle valeur aléatoire de 16 octets à chaque appel. Un identifiant de fichier différent modifie chaque clé dérivée, même lorsque le mot de passe est identique.

Tous les objets indirects d’un PDF chiffré partagent-ils une même clé?

Non. derive_object_key combine le numéro d’objet et le numéro de génération avec la clé de chiffrement du fichier, de sorte que chaque objet indirect obtient une clé distincte propre à l’objet.

Le code applicatif doit-il appeler EncryptionUtils directement pour protéger un PDF par mot de passe?

En général, non. EncryptionUtils est le moteur de bas niveau sur lequel reposent les API de mot de passe au niveau du document. La plupart des applications devraient protéger les PDF via le API au niveau du document et n’utiliser EncryptionUtils que pour des outils personnalisés ou une inspection légale de fichiers chiffrés.


Voir aussi

 Français