Como trabalhar com o mecanismo de criptografia PDF em Python

Como trabalhar com o mecanismo de criptografia PDF em Python

Aspose.PDF FOSS para Python (import aspose_pdf) implementa a proteção por senha de PDF ao nível de byte através de uma pequena classe de motor interno, EncryptionUtils, em aspose_pdf.engine.encryption. É aqui que o cifra AES-CBC e os algoritmos de derivação de chave do PDF Standard Security Handler (ISO 32000 Revisões 2 a 6) realmente residem, independentemente do Document de alto nível API. Este guia instala o pacote e percorre três tarefas do motor: criptografar e descriptografar bytes brutos com AES-CBC, derivar chaves do Standard Security Handler para uma senha (Revisões 2-4) e derivar chaves AES-256 para documentos das Revisões 5/6.

Guia passo a passo

Etapa 2: Importar classes necessárias

EncryptionUtils não é reexportado do pacote de nível superior aspose_pdf — importe-o diretamente do submódulo engine.encryption:

import os
from aspose_pdf.engine.encryption import EncryptionUtils

Etapa 3: Criptografar e descriptografar dados com AES-CBC

encrypt_aes_cbc e decrypt_aes_cbc trabalham diretamente em qualquer chave de 16, 24 ou 32 bytes (AES-128, AES-192 ou AES-256) sem passar pela derivação de chave baseada em senha. encrypt_aes_cbc aplica preenchimento PKCS7 internamente, e decrypt_aes_cbc o remove.

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

Etapa 4: Derivar Chaves Padrão do Manipulador de Segurança para uma Senha (Revisões 2-4)

compute_owner_key_v4 calcula o valor da senha de Proprietário (O) de acordo com o Algoritmo 3.3, e compute_user_key_v4 calcula o valor da senha de Usuário (U) e a chave de criptografia do arquivo conforme os Algoritmos 3.4/3.5. verify_password_v4 recalcula a chave a partir de uma senha candidata para que você possa confirmar que ela abre o documento antes de confiá-lo.

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 e u_value têm sempre 32 bytes independentemente de key_length. verify_password_v4 retorna None — não uma exceção — quando a senha não corresponde.


Etapa 5: Derivar Chaves AES-256 para Documentos de Revisão 5/6

Os manipuladores de segurança das revisões 5 e 6 (AES-256) não utilizam compute_owner_key_v4/compute_user_key_v4 — eles aplicam hash à senha com compute_hash_v5, que implementa o “Algoritmo 2.B” da ISO 32000-2. Use generate_file_id para um identificador de arquivo novo e derive_object_key para transformar uma chave de criptografia de arquivo em uma chave para um objeto indireto 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 mistura o número do objeto e o número de geração na chave do arquivo, de modo que cada objeto indireto receba sua própria chave, embora a chave do arquivo seja compartilhada.


Problemas Comuns e Soluções

encrypt_aes_cbc/decrypt_aes_cbc levanta “AES key must be 16, 24, or 32 bytes”. O argumento key deve ser uma chave AES real, não uma string de senha bruta. Gere uma com os.urandom(16 | 24 | 32), ou derive uma primeiro com compute_user_key_v4 (Revisions 2-4) ou compute_hash_v5 (Revisões 5/6).

verify_password_v4 returns None em vez de levantar. Isso significa que a senha não corresponde — não é uma condição de erro para capturar, apenas um resultado a ser verificado. Também confirme que permissions, file_id, key_length, e revision são os mesmos valores exatos usados quando o documento o_value and u_value foram originalmente calculados; uma incompatibilidade em qualquer um deles produz silenciosamente a chave errada em vez de falhar de forma evidente.

compute_owner_key_v4 com uma senha de proprietário vazia. Passing "" for owner_password é um comportamento definido pela especificação, não um bug — o algoritmo recorre ao uso da senha do usuário, de modo que as senhas de proprietário e de usuário se tornam equivalentes.

Chaves derivadas para a revisão ou comprimento de chave incorretos. A Revisão 2 usa chaves RC4 de 40 bits (5 bytes), as Revisões 3/4 usam chaves RC4/AES de 128 bits (16 bytes) e as Revisões 5/6 usam compute_hash_v5 em vez de compute_owner_key_v4/compute_user_key_v4 totalmente. Misturar esses não gera uma exceção — apenas produz uma chave que não abrirá o documento.

compute_hash_v5 a saída muda inesperadamente. O terceiro argumento (user_key) é misturado ao hash. Passe b"" explicitamente quando você deseja um hash simples de validação de senha, pois omiti-lo não é o mesmo que passar um valor vazio em todos os pontos de chamada.


Perguntas Frequentes

Por que EncryptionUtils não pode ser importado diretamente de aspose_pdf?

Ele está no subpacote engine porque é um detalhe interno de implementação da proteção por senha a nível de documento, não faz parte da interface pública de alto nível API. Importe-o explicitamente de aspose_pdf.engine.encryption.

É compute_hash_v5 o mesmo que uma única chamada SHA-256?

Não. Implementa o Algoritmo 2.B da ISO 32000-2, que executa múltiplas rodadas usando diferentes algoritmos de hash selecionados por valores intermediários — deliberadamente mais caro que uma única chamada de hash.

O generate_file_id() retorna os mesmos bytes todas as vezes?

Não — retorna um novo valor aleatório de 16 bytes a cada chamada. Um identificador de arquivo diferente altera cada chave derivada dele, mesmo quando a senha é idêntica.

Todos os objetos indiretos em um PDF criptografado compartilham a mesma chave?

Não. derive_object_key combina o número do objeto e o número de geração com a chave de criptografia do arquivo, de modo que cada objeto indireto recebe uma chave distinta por objeto.

O código da aplicação deve chamar EncryptionUtils diretamente para proteger um PDF com senha?

Normalmente não. EncryptionUtils é o mecanismo de baixo nível sobre o qual as APIs de senha ao nível de documento são construídas. A maioria das aplicações deve proteger PDFs através do API ao nível de documento e recorrer a EncryptionUtils apenas para ferramentas personalizadas ou inspeção forense de arquivos criptografados.


Ver também

 Português