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 EncryptionUtilsEtapa 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) == 16derive_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.