Extração de dados Python de um PDF criptografado

12

Sou recém-formado em matemática pura e cursou apenas alguns cursos básicos de programação. Estou fazendo um estágio e tenho um projeto interno de análise de dados. Eu tenho que analisar os PDFs internos dos últimos anos. Os PDFs são "protegidos". Em outras palavras, eles são criptografados. Não temos senhas em PDF, mais ainda, não temos certeza se existem senhas. Mas, temos todos esses documentos e podemos lê-los manualmente. Também podemos imprimi-los. O objetivo é lê-los com Python, porque é a linguagem que temos alguma idéia.

Primeiro, tentei ler os PDFs com algumas bibliotecas Python. No entanto, as bibliotecas Python que encontrei não leem PDFs criptografados. Naquele momento, também não era possível exportar as informações usando o Adobe Reader.

Segundo, decidi decifrar os PDFs. Eu tive sucesso usando a biblioteca Python pykepdf. Pykepdf funciona muito bem! No entanto, os PDFs descriptografados também não podem ser lidos com as bibliotecas Python do ponto anterior ( PyPDF2 e Tabula ). No momento, fizemos algumas melhorias porque, usando o Adobe Reader I, posso exportar as informações dos PDFs descriptografados, mas o objetivo é fazer tudo com o Python.

O código que estou mostrando funciona perfeitamente com PDFs não criptografados, mas não com PDFs criptografados. Ele não está funcionando com os PDFs descriptografados que foram obtidos com o pykepdf também.

Eu não escrevi o código. Encontrei-o na documentação das bibliotecas Python Pykepdf e Tabula . A solução PyPDF2 foi escrita por Al Sweigart em seu livro " Automatize o material chato com o Python ", que eu recomendo. Também verifiquei se o código está funcionando bem, com as limitações que expliquei antes.

Primeira pergunta, por que não consigo ler os arquivos descriptografados, se os programas funcionam com arquivos que nunca foram criptografados?

Segunda pergunta: Podemos ler com Python os arquivos descriptografados de alguma forma? Qual biblioteca pode fazer isso ou é impossível? Todos os PDFs descriptografados são extraíveis?

Obrigado pelo seu tempo e ajuda!!!

Encontrei esses resultados usando Python 3.7, Windows 10, Jupiter Notebooks e Anaconda 2019.07.

Python

import pikepdf
with pikepdf.open("encrypted.pdf") as pdf:
  num_pages = len(pdf.pages)
  del pdf.pages[-1]
  pdf.save("decrypted.pdf")

import tabula
tabula.read_pdf("decrypted.pdf", stream=True)

import PyPDF2
pdfFileObj=open("decrypted.pdf", "rb")
pdfReader=PyPDF2.PdfFileReader(pdfFileObj)
pdfReader.numPages
pageObj=pdfReader.getPage(0)
pageObj.extractText()

Com o Tabula, estou recebendo a mensagem "o arquivo de saída está vazio".

Com PyPDF2, estou recebendo apenas '/ n'

ATUALIZAÇÃO 10/03/2019 Pdfminer.six (versão novembro de 2018)

Eu obtive melhores resultados usando a solução postada pelo DuckPuncher . Para o arquivo descriptografado, obtive os rótulos, mas não os dados. O mesmo acontece com o arquivo criptografado. Para o arquivo que nunca foi criptografado funciona perfeitamente. Como preciso dos dados e dos rótulos dos arquivos criptografados ou descriptografados, esse código não funciona para mim. Para essa análise, usei o pdfminer.six, que é a biblioteca Python, lançada em novembro de 2018. O pdfminer.six inclui um pycryptodome da biblioteca. De acordo com a documentação " PyCryptodome é um pacote Python independente de primitivas criptográficas de baixo nível .."

O código está na pergunta de troca de pilhas: Extraindo texto de um arquivo PDF usando o PDFMiner em python?

Eu adoraria se você quiser repetir meu experimento. Aqui está a descrição:

1) Execute os códigos mencionados nesta pergunta com qualquer PDF que nunca tenha sido criptografado.

2) Faça o mesmo com um PDF "Seguro" (este é o termo usado pela Adobe), estou chamando de PDF criptografado. Use um formulário genérico que você pode encontrar usando o Google. Depois de fazer o download, você precisa preencher os campos. Caso contrário, você estaria verificando rótulos, mas não campos. Os dados estão nos campos.

3) Descriptografe o PDF criptografado usando Pykepdf. Este será o PDF descriptografado.

4) Execute os códigos novamente usando o PDF descriptografado.

ATUALIZAÇÃO 10/4/2019 Camelot (versão julho de 2019)

Eu encontrei a biblioteca Python Camelot. Cuidado para precisar do camelot-py 0.7.3.

É muito poderoso e funciona com o Python 3.7. Além disso, é muito fácil de usar. Primeiro, você também precisa instalar o Ghostscript . Caso contrário, não funcionará. Você também precisa instalar o Pandas . Não use pip install camelot-py . Em vez disso, use pip install camelot-py [cv]

O autor do programa é Vinayak Mehta. Frank Du compartilha esse código em um vídeo do YouTube "Extrair dados tabulares do PDF com o Camelot Using Python".

Eu verifiquei o código e ele está funcionando com arquivos não criptografados. No entanto, ele não funciona com arquivos criptografados e descriptografados, e esse é o meu objetivo .

Camelot é orientado a obter tabelas de PDFs.

Aqui está o código:

Python

import camelot
import pandas
name_table = camelot.read_pdf("uncrypted.pdf")
type(name_table)

#This is a Pandas dataframe
name_table[0]

first_table = name_table[0]   

#Translate camelot table object to a pandas dataframe
first_table.df

first_table.to_excel("unencrypted.xlsx")
#This creates an excel file.
#Same can be done with csv, json, html, or sqlite.

#To get all the tables of the pdf you need to use this code.
for table in name_table:
   print(table.df)

ATUALIZAÇÃO 10/7/2019 Encontrei um truque. Se eu abrir o PDF protegido com o Adobe Reader e imprimi-lo usando o Microsoft para PDF, e salvá-lo como PDF, posso extrair os dados usando essa cópia. Também posso converter o arquivo PDF em JSON, Excel, SQLite, CSV, HTML e outros formatos. Esta é uma solução possível para minha pergunta. No entanto, ainda estou procurando uma opção para fazer isso sem esse truque, porque o objetivo é fazê-lo 100% com Python. Também estou preocupado que, se um método melhor de criptografia for usado, o truque talvez não funcione. Às vezes, você precisa usar o Adobe Reader várias vezes para obter uma cópia extraível.

ATUALIZAÇÃO 10/08/2019. Terceira pergunta. Agora tenho uma terceira pergunta. Todos os PDFs protegidos / criptografados são protegidos por senha? Por que pikepdf não está funcionando? Meu palpite é que a versão atual do pikepdf pode quebrar algum tipo de criptografia, mas não todas. O @constt mencionou que o PyPDF2 pode quebrar algum tipo de proteção. No entanto, respondi que encontrei um artigo em que o PyPDF2 pode quebrar as criptografias feitas com o Adobe Acrobat Pro 6.0, mas não nas versões posteriores.

Principiante
fonte
2
Não consegui reproduzir esses problemas PyPDF2, tudo funciona muito bem. Eu usei pdftk, assim como serviços online, para criptografar arquivos. Você pode postar links para arquivos pdf "problemáticos"?
constt 7/10/19
11
OK, obrigado! Você já tentou usar qpdfpara descriptografar seus arquivos? Caso funcione, você pode chamá-lo do seu script usando o subprocessmódulo para descriptografar arquivos antes de analisá-los.
constt 8/10/19
11
Primeiro, o PyPDF2 não pode descriptografar arquivos PDF do Acrobat => 6.0. Segundo, o pikepdf atualmente não possui um implemento de extração de texto.
vida é complexa
11
@Beginner Gostaria de especular que isso tenha a ver com a formatação subjacente usada pelo pykepdf para escrever o PDF não criptografado.
vida é complexa
2
"Todos os PDFs protegidos / criptografados são protegidos por senha?" - não. Também existem PDFs criptografados usando criptografia de chave pública / privada com base em certificados X509.
Mkl

Respostas:

8

ÚLTIMA ATUALIZAÇÃO 10-11-2019

Não tenho certeza se entendi completamente sua pergunta. O código abaixo pode ser refinado, mas lê um PDF criptografado ou não criptografado e extrai o texto. Informe-me se eu não entendi seus requisitos.

from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
from io import StringIO

def extract_encrypted_pdf_text(path, encryption_true, decryption_password):

  output = StringIO()

  resource_manager = PDFResourceManager()
  laparams = LAParams()

  device = TextConverter(resource_manager, output, codec='utf-8', laparams=laparams)

  pdf_infile = open(path, 'rb')
  interpreter = PDFPageInterpreter(resource_manager, device)

  page_numbers = set()

  if encryption_true == False:
    for page in PDFPage.get_pages(pdf_infile, page_numbers, maxpages=0, caching=True, check_extractable=True):
      interpreter.process_page(page)

  elif encryption_true == True:
    for page in PDFPage.get_pages(pdf_infile, page_numbers, maxpages=0, password=decryption_password, caching=True, check_extractable=True):
      interpreter.process_page(page)

 text = output.getvalue()
 pdf_infile.close()
 device.close()
 output.close()
return text

results = extract_encrypted_pdf_text('encrypted.pdf', True, 'password')
print (results)

Observei que faltava uma senha no seu código pikepdf usado para abrir um PDF criptografado, o que deveria ter gerado essa mensagem de erro:

pikepdf._qpdf.PasswordError: encrypted.pdf: senha inválida

import pikepdf

with pikepdf.open("encrypted.pdf", password='password') as pdf:
num_pages = len(pdf.pages)
del pdf.pages[-1]
pdf.save("decrypted.pdf")

Você pode usar o tika para extrair o texto do decrypted.pdf criado por pikepdf .

from tika import parser

parsedPDF = parser.from_file("decrypted.pdf")
pdf = parsedPDF["content"]
pdf = pdf.replace('\n\n', '\n')

Além disso, o pikepdf atualmente não implementa a extração de texto, incluindo a versão mais recente v1.6.4.


Decidi executar alguns testes usando vários arquivos PDF criptografados.

Chamei todos os arquivos criptografados de 'encrypted.pdf' e todos usaram a mesma senha de criptografia e descriptografia.

  1. Adobe Acrobat 9.0 e posterior - nível de criptografia AES de 256 bits

    • pikepdf conseguiu descriptografar este arquivo
    • PyPDF2 não pôde extrair o texto corretamente
    • tika poderia extrair o texto corretamente
  2. Adobe Acrobat 6.0 e posterior - nível de criptografia RC4 de 128 bits

    • pikepdf conseguiu descriptografar este arquivo
    • PyPDF2 não pôde extrair o texto corretamente
    • tika poderia extrair o texto corretamente
  3. Adobe Acrobat 3.0 e posterior - nível de criptografia RC4 de 40 bits

    • pikepdf conseguiu descriptografar este arquivo
    • PyPDF2 não pôde extrair o texto corretamente
    • tika poderia extrair o texto corretamente
  4. Adobe Acrobat 5.0 e posterior - nível de criptografia RC4 de 128 bits

    • criado com o Microsoft Word
    • pikepdf conseguiu descriptografar este arquivo
    • PyPDF2 pode extrair o texto corretamente
    • tika poderia extrair o texto corretamente
  5. Adobe Acrobat 9.0 e posterior - nível de criptografia AES de 256 bits

    • criado usando pdfprotectfree
    • pikepdf conseguiu descriptografar este arquivo
    • PyPDF2 pode extrair o texto corretamente
    • tika poderia extrair o texto corretamente

O PyPDF2 conseguiu extrair texto de arquivos PDF descriptografados não criados com o Adobe Acrobat.

Suponho que as falhas tenham algo a ver com a formatação incorporada nos PDFs criados pelo Adobe Acrobat. São necessários mais testes para confirmar essa conjectura sobre a formatação.

tika foi capaz de extrair texto de todos os documentos descriptografados com pikepdf.


 import pikepdf
 with pikepdf.open("encrypted.pdf", password='password') as pdf:
    num_pages = len(pdf.pages)
    del pdf.pages[-1]
    pdf.save("decrypted.pdf")


 from PyPDF2 import PdfFileReader

 def text_extractor(path):
   with open(path, 'rb') as f:
     pdf = PdfFileReader(f)
     page = pdf.getPage(1)
     print('Page type: {}'.format(str(type(page))))
     text = page.extractText()
     print(text)

    text_extractor('decrypted.pdf')

PyPDF2 não pode descriptografar arquivos PDF do Acrobat => 6.0

Este problema está aberto com os proprietários do módulo desde 15 de setembro de 2015 . Não está claro nos comentários relacionados a esse problema quando esse problema será corrigido pelos proprietários do projeto. O último commit foi em 25 de junho de 2018.

Problemas de descriptografia do PyPDF4

PyPDF4 é o substituto do PyPDF2. Este módulo também possui problemas de descriptografia com certos algoritmos usados ​​para criptografar arquivos PDF.

arquivo de teste: Adobe Acrobat 9.0 e posterior - nível de criptografia AES de 256 bits

Mensagem de erro do PyPDF2: somente códigos de algoritmo 1 e 2 são suportados

Mensagem de erro do PyPDF4: somente os códigos 1 e 2 do algoritmo são suportados. Este PDF usa o código 5


SEÇÃO DE ATUALIZAÇÃO 10-11-2019

Esta seção é uma resposta às suas atualizações em 10-10-2019 e 10-08-2019.

Na sua atualização, você declarou que podia abrir um 'pdf protegido com Adobe Reader' e imprimir o documento em outro PDF, o que remove o sinalizador 'SEGURO'. Depois de fazer alguns testes, acredito que descobrimos o que está ocorrendo nesse cenário.

Nível de segurança do Adobe PDFs

Os PDFs da Adobe têm vários tipos de controles de segurança que podem ser ativados pelo proprietário do documento. Os controles podem ser aplicados com uma senha ou um certificado.

  1. Criptografia de documento (imposta com uma senha de abertura de documento)

    • Criptografar todo o conteúdo do documento (mais comum)
    • Criptografar todo o conteúdo do documento, exceto os metadados => Acrobat 6.0
    • Criptografar apenas anexos de arquivo => Acrobat 7.0
  2. Edição e impressão restritivas (aplicadas com uma senha de permissão)

    • Impressão permitida
    • Alterações permitidas

A imagem abaixo mostra um Adobe PDF sendo criptografado com criptografia AES de 256 bits. Para abrir ou imprimir este PDF, é necessária uma senha. Quando você abre este documento no Adobe Reader com a senha, o título indica SEGURO

password_level_encryption

Este documento requer uma senha para abrir com os módulos Python mencionados nesta resposta. Se você tentar abrir um PDF criptografado com o Adobe Reader. Você deve ver isso:

password_prompt

Se você não receber esse aviso, o documento não possui controles de segurança ativados ou apenas os restritivos de edição e impressão ativados.

A imagem abaixo mostra a edição restritiva sendo ativada com uma senha em um documento PDF. A impressão de notas está ativada . Para abrir ou imprimir este PDF, uma senha não é necessária . Quando você abre este documento no Adobe Reader sem uma senha, o título indica SEGURO. É o mesmo aviso que o PDF criptografado que foi aberto com uma senha.

Quando você imprime este documento em um novo PDF, o aviso SEGURO é removido, porque a edição restritiva foi removida.

password_level_restrictive_editing

Todos os produtos da Adobe aplicam as restrições definidas pela senha de permissões. No entanto, se produtos de terceiros não suportarem essas configurações, os destinatários do documento poderão ignorar algumas ou todas as restrições definidas.

Portanto, suponho que o documento que você está imprimindo em PDF tenha a edição restritiva ativada e não tenha uma senha necessária para abrir ativada.

Sobre quebra de criptografia PDF

Nem o PyPDF2 nem o PyPDF4 foram projetados para quebrar a função de senha de abertura de documento de um documento PDF. Ambos os módulos lançarão o seguinte erro se tentarem abrir um arquivo PDF protegido por senha criptografado.

PyPDF2.utils.PdfReadError: o arquivo não foi descriptografado

A função de senha de abertura de um arquivo PDF criptografado pode ser ignorada usando uma variedade de métodos, mas uma única técnica pode não funcionar e algumas não serão aceitáveis ​​devido a vários fatores, incluindo a complexidade da senha.

A criptografia de PDF funciona internamente com chaves de criptografia de 40, 128 ou 256 bits, dependendo da versão do PDF. A chave de criptografia binária é derivada de uma senha fornecida pelo usuário. A senha está sujeita a restrições de comprimento e codificação.

Por exemplo, o PDF 1.7 Adobe Extension Level 3 (Acrobat 9 - AES-256) introduziu caracteres Unicode (65.536 caracteres possíveis) e aumentou o comprimento máximo para 127 bytes na representação UTF-8 da senha.


O código abaixo abrirá um PDF com a edição restritiva ativada. Ele salvará esse arquivo em um novo PDF sem que o aviso SECURED seja adicionado. O código tika analisará o conteúdo do novo arquivo.

from tika import parser
import pikepdf

# opens a PDF with restrictive editing enabled, but that still 
# allows printing.
with pikepdf.open("restrictive_editing_enabled.pdf") as pdf:
  pdf.save("restrictive_editing_removed.pdf")

  # plain text output
  parsedPDF = parser.from_file("restrictive_editing_removed.pdf")

  # XHTML output
  # parsedPDF = parser.from_file("restrictive_editing_removed.pdf", xmlContent=True)

  pdf = parsedPDF["content"]
  pdf = pdf.replace('\n\n', '\n')
  print (pdf)

Este código verifica se é necessária uma senha para abrir o arquivo. Este código pode ser refinado e outras funções podem ser adicionadas. Existem vários outros recursos que podem ser adicionados, mas a documentação do pikepdf não corresponde aos comentários na base de código; portanto, são necessárias mais pesquisas para melhorar isso.

# this would be removed once logging is used
############################################
import sys
sys.tracebacklimit = 0
############################################

import pikepdf
from tika import parser

def create_pdf_copy(pdf_file_name):
  with pikepdf.open(pdf_file_name) as pdf:
    new_filename = f'copy_{pdf_file_name}'
    pdf.save(new_filename)
    return  new_filename

def extract_pdf_content(pdf_file_name):
  # plain text output
  # parsedPDF = parser.from_file("restrictive_editing_removed.pdf")

  # XHTML output
  parsedPDF = parser.from_file(pdf_file_name, xmlContent=True)

  pdf = parsedPDF["content"]
  pdf = pdf.replace('\n\n', '\n')
  return pdf

def password_required(pdf_file_name):
  try:
    pikepdf.open(pdf_file_name)

  except pikepdf.PasswordError as error:
    return ('password required')

  except pikepdf.PdfError as results:
    return ('cannot open file')


filename = 'decrypted.pdf'
password = password_required(filename)
if password != None:
  print (password)
elif password == None:
  pdf_file = create_pdf_copy(filename)
  results = extract_pdf_content(pdf_file)
  print (results)
A vida é complexa
fonte
2
Como você está abrindo um arquivo PDF seguro sem fornecer uma senha?
vida é complexa
11
Você está se referindo apenas à proteção restrita de edição?
vida é complexa
11
Resposta atualizada com o código que funcionava com um PDF que tinha proteção restrita de edição ativada, mas permitia impressão.
vida é complexa
11
Você pode usar XHTML?
vida é complexa
11
Modifiquei a resposta para gerar XHTML. O JSON é possível, mas requer a inserção do código do projeto no github relacionado ao analisador tika.
vida é complexa
1

Você pode tentar lidar com o erro que esses arquivos produzem quando você os abre sem uma senha.

import pikepdf

def open_pdf(pdf_file_path, pdf_password=''):
    try:
        pdf_obj = pikepdf.Pdf.open(pdf_file_path)

    except pikepdf._qpdf.PasswordError:
        pdf_obj = pikepdf.Pdf.open(pdf_file_path, password=pdf_password)

    finally:
        return pdf_obj

Você pode usar o pdf_obj retornado para o seu trabalho de análise. Além disso, você pode fornecer a senha caso tenha um PDF criptografado.

Mahendra Singh
fonte
11
Obrigado pela sua resposta! Estamos tentando lê-lo sem uma senha. No momento, pudemos fazer isso com o método explicado em minha ATUALIZAÇÃO 10/10/2019
Iniciante
Isso está longe de responder à pergunta. Parece que você não leu a pergunta completa.
shoonya ek
11
Isso lida com os PDFs protegidos onde normalmente o pikepdf falha quando o valor padrão da senha é Nenhum. Ao passar uma sequência vazia, é possível abrir e analisar um documento PDF protegido corretamente (nos casos de teste que eu executei).
Mahendra Singh
11
@ Iniciante: você não precisa converter os PDFs aqui neste caso. Isso é apenas da minha experiência anterior que os PDFs protegidos funcionam fornecendo uma senha vazia.
Mahendra Singh
11
@ Iniciante, este é o meu código inteiro. Isso retorna apenas o pdf_object do pikepdf. Caso você queira salvar este pdf, salve o objeto retornado usando pdf_obj.save ('your_file_path'). Depois disso, você pode usar este PDF para analisar texto e outros objetos. Eu uso uma biblioteca chamada PdfPlumber para extração de texto.
Mahendra Singh
1

Para tabula-py, você pode tentar a opção de senha com read_pdf. Depende da função do tabula-java, então não tenho certeza de qual criptografia é suportada.

chezou
fonte