Cómo trabajar con el motor de cifrado PDF en Python

Cómo trabajar con el motor de cifrado PDF en Python

Aspose.PDF FOSS para Python (import aspose_pdf) implementa la protección por contraseña PDF a nivel de byte mediante una pequeña clase interna de motor, EncryptionUtils, en aspose_pdf.engine.encryption. Aquí es donde residen el cifrado AES-CBC y los algoritmos de derivación de claves del PDF Standard Security Handler (ISO 32000 Revisiones 2 a 6), de forma independiente del Document de alto nivel API. Esta guía instala el paquete y recorre tres tareas del motor: cifrar y descifrar bytes sin procesar con AES-CBC, derivar claves del Standard Security Handler para una contraseña (Revisiones 2-4) y derivar claves AES-256 para documentos de Revisión 5/6.

Guía paso a paso

Paso 2: Importar clases requeridas

EncryptionUtils no se reexporta desde el paquete de nivel superior aspose_pdf — impórtalo directamente desde el submódulo engine.encryption:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

Paso 3: Cifrar y descifrar datos con AES-CBC

encrypt_aes_cbc y decrypt_aes_cbc funcionan directamente con cualquier clave de 16, 24 o 32 bytes (AES-128, AES-192 o AES-256) sin pasar por la derivación de claves basada en contraseñas. encrypt_aes_cbc aplica el relleno PKCS7 internamente, y decrypt_aes_cbc lo elimina.

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

Paso 4: Derivar claves del controlador de seguridad estándar para una contraseña (Revisiones 2-4)

compute_owner_key_v4 calcula el valor de la contraseña del propietario (O) según el Algoritmo 3.3, y compute_user_key_v4 calcula el valor de la contraseña de usuario (U) y la clave de cifrado del archivo según los Algoritmos 3.4/3.5. verify_password_v4 vuelve a derivar la clave a partir de una contraseña candidata para que puedas confirmar que abre el documento antes de confiar en él.

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 y u_value siempre son de 32 bytes sin importar key_length. verify_password_v4 devuelve None — no una excepción — cuando la contraseña no coincide.


Paso 5: Derivar claves AES-256 para documentos de revisión 5/6

Los controladores de seguridad de las revisiones 5 y 6 (AES-256) no utilizan compute_owner_key_v4/compute_user_key_v4 — hash la contraseña con compute_hash_v5, que implementa el “Algoritmo 2.B” de ISO 32000-2. Usa generate_file_id para obtener un identificador de archivo nuevo, y derive_object_key para convertir una clave de cifrado del archivo en una clave para un objeto indirecto específico.

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 mezcla el número de objeto y el número de generación en la clave del archivo, de modo que cada objeto indirecto obtenga su propia clave aunque la clave del archivo se comparta.


Problemas comunes y soluciones

encrypt_aes_cbc/decrypt_aes_cbc lanza “AES key must be 16, 24, or 32 bytes”. El argumento key debe ser una clave AES real, no una cadena de contraseña sin procesar. Genera una con os.urandom(16 | 24 | 32), o deriva una primero con compute_user_key_v4 (Revisions 2-4) o compute_hash_v5 (Revisiones 5/6).

verify_password_v4 returns None en lugar de lanzar. Esto significa que la contraseña no coincide — no es una condición de error para capturar, solo un resultado a verificar. También confirma que permissions, file_id, key_length, y revision son los mismos valores exactos que se usaron cuando el documento o_value and u_value fueron calculados originalmente; una discrepancia en cualquiera de ellos produce silenciosamente la clave incorrecta en lugar de fallar ruidosamente.

compute_owner_key_v4 con una contraseña de propietario vacía. Passing "" for owner_password es un comportamiento definido por la especificación, no un error — el algoritmo recurre a usar la contraseña de usuario, por lo que las contraseñas de propietario y de usuario se vuelven equivalentes.

Claves derivadas para la revisión o longitud de clave incorrectas. La revisión 2 usa claves RC4 de 40 bits (5 bytes), la revisión 3/4 usa claves RC4/AES de 128 bits (16 bytes), y la revisión 5/6 usa compute_hash_v5 en lugar de compute_owner_key_v4/compute_user_key_v4 por completo. Mezclar estos no genera una excepción — solo produce una clave que no abrirá el documento.

compute_hash_v5 la salida cambia inesperadamente. El tercer argumento (user_key) se mezcla en el hash. Pase b"" explícitamente cuando desea un hash simple de validación de contraseña, ya que omitirlo no es lo mismo que pasar un valor vacío en cada sitio de llamada.


Preguntas Frecuentes

¿Por qué EncryptionUtils no es importable directamente desde aspose_pdf?

Reside en el subpaquete engine porque es un detalle de implementación interno de la protección de contraseñas a nivel de documento, no forma parte de la superficie pública de alto nivel API. Impórtelo explícitamente desde aspose_pdf.engine.encryption.

¿Es compute_hash_v5 lo mismo que una única llamada a SHA-256?

No. Implementa el Algoritmo 2.B de ISO 32000-2, que realiza múltiples rondas utilizando diferentes algoritmos de hash seleccionados por valores intermedios — deliberadamente más costoso que una única llamada de hash.

¿Devuelve generate_file_id() los mismos bytes cada vez?

No — devuelve un valor nuevo y aleatorio de 16 bytes en cada llamada. Un identificador de archivo diferente cambia cada clave derivada de él, incluso cuando la contraseña es idéntica.

¿Comparten todos los objetos indirectos en un PDF cifrado una única clave?

No. derive_object_key combina el número de objeto y el número de generación con la clave de cifrado del archivo, de modo que cada objeto indirecto obtiene una clave distinta por objeto.

¿Debería el código de la aplicación llamar a EncryptionUtils directamente para proteger con contraseña un PDF?

Normalmente no. EncryptionUtils es el motor de bajo nivel sobre el que se construyen las API de contraseñas a nivel de documento. La mayoría de las aplicaciones deberían proteger los PDFs a través del API a nivel de documento y recurrir a EncryptionUtils solo para herramientas personalizadas o inspección forense de archivos cifrados.


Ver también

 Español