Extracción de datos de Python desde un PDF cifrado

12

Soy un recién graduado en matemáticas puras que solo ha tomado algunos cursos básicos de programación. Estoy haciendo una pasantía y tengo un proyecto de análisis de datos interno. Tengo que analizar los PDF internos de los últimos años. Los archivos PDF están "asegurados". En otras palabras, están encriptados. No tenemos contraseñas en PDF, aún más, no estamos seguros de si existen contraseñas. Pero tenemos todos estos documentos y podemos leerlos manualmente. También podemos imprimirlos. El objetivo es leerlos con Python porque es el lenguaje que tenemos alguna idea.

Primero, intenté leer los archivos PDF con algunas bibliotecas de Python. Sin embargo, las bibliotecas de Python que encontré no leen archivos PDF encriptados. En ese momento, tampoco podía exportar la información con Adobe Reader.

En segundo lugar, decidí descifrar los archivos PDF. Tuve éxito usando la biblioteca Python pykepdf. ¡Pykepdf funciona muy bien! Sin embargo, los PDF descifrados no se pueden leer tan bien con las bibliotecas de Python del punto anterior ( PyPDF2 y Tabula ). En este momento, hemos realizado algunas mejoras porque con Adobe Reader puedo exportar la información de los PDF descifrados, pero el objetivo es hacer todo con Python.

El código que estoy mostrando funciona perfectamente con PDF sin cifrar, pero no con PDF cifrados. No funciona con los PDF descifrados que también se obtuvieron con pykepdf.

No escribí el código. Lo encontré en la documentación de las bibliotecas de Python Pykepdf y Tabula . La solución PyPDF2 fue escrita por Al Sweigart en su libro, " Automatice las cosas aburridas con Python ", que recomiendo encarecidamente. También verifiqué que el código funciona bien, con las limitaciones que expliqué antes.

Primera pregunta, ¿por qué no puedo leer los archivos descifrados, si los programas funcionan con archivos que nunca se han cifrado?

Segunda pregunta, ¿podemos leer con Python los archivos descifrados de alguna manera? ¿Qué biblioteca puede hacerlo o es imposible? ¿Todos los PDF descifrados son extraíbles?

¡¡¡Gracias por su tiempo y ayuda!!!

Encontré estos resultados usando Python 3.7, Windows 10, Jupiter Notebooks y 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()

Con Tabula, recibo el mensaje "el archivo de salida está vacío".

Con PyPDF2, solo obtengo '/ n'

ACTUALIZACIÓN 10/3/2019 Pdfminer.six (Versión de noviembre de 2018)

Obtuve mejores resultados usando la solución publicada por DuckPuncher . Para el archivo descifrado, obtuve las etiquetas, pero no los datos. Lo mismo sucede con el archivo encriptado. Para el archivo que nunca ha sido encriptado funciona perfecto. Como necesito los datos y las etiquetas de los archivos cifrados o descifrados, este código no me funciona. Para ese análisis, utilicé pdfminer.six, que es la biblioteca Python que se lanzó en noviembre de 2018. Pdfminer.six incluye una biblioteca pycryptodome. Según su documentación, " PyCryptodome es un paquete autónomo de Python de primitivas criptográficas de bajo nivel ..."

El código está en la pregunta de intercambio de pila: ¿ Extraer texto de un archivo PDF usando PDFMiner en Python?

Me encantaría si quieres repetir mi experimento. Aquí está la descripción:

1) Ejecute los códigos mencionados en esta pregunta con cualquier PDF que nunca haya sido encriptado.

2) Haga lo mismo con un PDF "Seguro" (este es un término que utiliza Adobe), lo llamo PDF encriptado. Use un formulario genérico que puede encontrar usando Google. Después de descargarlo, debe completar los campos. De lo contrario, estaría buscando etiquetas, pero no campos. Los datos están en los campos.

3) Descifre el PDF encriptado usando Pykepdf. Este será el PDF descifrado.

4) Ejecute los códigos nuevamente usando el PDF descifrado.

ACTUALIZACIÓN 10/4/2019 Camelot (Versión Julio 2019)

Encontré la biblioteca de Python Camelot. Tenga cuidado de que necesita camelot-py 0.7.3.

Es muy potente y funciona con Python 3.7. Además, es muy fácil de usar. Primero, también necesita instalar Ghostscript . De lo contrario, no funcionará. También necesitas instalar Pandas . No utilice pip install camelot-py . En su lugar, use pip install camelot-py [cv]

El autor del programa es Vinayak Mehta. Frank Du comparte este código en un video de YouTube "Extraiga datos tabulares de PDF con Camelot usando Python".

Verifiqué el código y funciona con archivos sin cifrar. Sin embargo, no funciona con archivos cifrados y descifrados, y ese es mi objetivo .

Camelot está orientado a obtener tablas de archivos PDF.

Aquí está el 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)

ACTUALIZACIÓN 10/7/2019 Encontré un truco. Si abro el pdf seguro con Adobe Reader, y lo imprimo usando Microsoft a PDF, y lo guardo como PDF, puedo extraer los datos usando esa copia. También puedo convertir el archivo PDF a JSON, Excel, SQLite, CSV, HTML y otros formatos. Esta es una posible solución a mi pregunta. Sin embargo, todavía estoy buscando una opción para hacerlo sin ese truco porque el objetivo es hacerlo al 100% con Python. También me preocupa que si se usa un mejor método de cifrado, el truco tal vez no funcione. A veces necesita usar Adobe Reader varias veces para obtener una copia extraíble.

ACTUALIZACIÓN 10/8/2019. Tercera pregunta Ahora tengo una tercera pregunta. ¿Todos los archivos PDF seguros / encriptados están protegidos con contraseña? ¿Por qué pikepdf no funciona? Supongo que la versión actual de pikepdf puede romper algún tipo de cifrado, pero no todos. @constt mencionó que PyPDF2 puede romper algún tipo de protección. Sin embargo, le respondí que encontré un artículo que PyPDF2 puede romper los cifrados hechos con Adobe Acrobat Pro 6.0, pero no con versiones posteriores.

Principiante
fuente
2
No pude reproducir estos problemas PyPDF2, todo funciona bien. Utilicé pdftkasí como servicios en línea para encriptar archivos. ¿Puedes publicar enlaces a archivos pdf "problemáticos"?
Constt
1
¡OK gracias! ¿Has intentado usar qpdfpara descifrar tus archivos? En el caso de que funcione, puede llamarlo desde su script usando el subprocessmódulo para descifrar archivos antes de analizarlos.
Constt
1
Primero, PyPDF2 no puede descifrar archivos PDF de Acrobat => 6.0. En segundo lugar, pikepdf actualmente no tiene implemento de extracción de texto.
vida es compleja el
1
@Beginner Yo especularía que esto tiene que ver con el formato subyacente utilizado por pykepdf para escribir el PDF sin cifrar.
vida es compleja el
2
"¿Todos los archivos PDF seguros / encriptados están protegidos con contraseña?" - No. También hay archivos PDF cifrados con criptografía de clave pública / privada basada en certificados X509.
mkl

Respuestas:

8

ÚLTIMA ACTUALIZACIÓN 10-11-2019

No estoy seguro si entiendo tu pregunta por completo. El siguiente código se puede refinar, pero se lee en un PDF cifrado o no cifrado y extrae el texto. Avíseme si he entendido mal sus 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)

Noté que a su código pikepdf utilizado para abrir un PDF encriptado le faltaba una contraseña, lo que debería haber arrojado este mensaje de error:

pikepdf._qpdf.PasswordError: encrypted.pdf: contraseña no vá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")

Puede usar tika para extraer el texto del decrypted.pdf creado por pikepdf .

from tika import parser

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

Además, pikepdf no implementa actualmente la extracción de texto, esto incluye la última versión v1.6.4.


Decidí hacer un par de pruebas usando varios archivos PDF encriptados.

Llamé a todos los archivos cifrados 'encrypted.pdf' y todos usaron la misma contraseña de cifrado y descifrado.

  1. Adobe Acrobat 9.0 y posterior: nivel de cifrado AES de 256 bits

    • pikepdf pudo descifrar este archivo
    • PyPDF2 no pudo extraer el texto correctamente
    • tika podría extraer el texto correctamente
  2. Adobe Acrobat 6.0 y posterior - nivel de cifrado RC4 de 128 bits

    • pikepdf pudo descifrar este archivo
    • PyPDF2 no pudo extraer el texto correctamente
    • tika podría extraer el texto correctamente
  3. Adobe Acrobat 3.0 y posterior: nivel de cifrado RC4 de 40 bits

    • pikepdf pudo descifrar este archivo
    • PyPDF2 no pudo extraer el texto correctamente
    • tika podría extraer el texto correctamente
  4. Adobe Acrobat 5.0 y posterior - nivel de cifrado RC4 de 128 bits

    • creado con Microsoft Word
    • pikepdf pudo descifrar este archivo
    • PyPDF2 podría extraer el texto correctamente
    • tika podría extraer el texto correctamente
  5. Adobe Acrobat 9.0 y posterior: nivel de cifrado AES de 256 bits

    • creado usando pdfprotectfree
    • pikepdf pudo descifrar este archivo
    • PyPDF2 podría extraer el texto correctamente
    • tika podría extraer el texto correctamente

PyPDF2 pudo extraer texto de archivos PDF descifrados no creados con Adobe Acrobat.

Supongo que las fallas tienen algo que ver con el formato incrustado en los archivos PDF creados por Adobe Acrobat. Se requieren más pruebas para confirmar esta conjetura sobre el formateo.

tika pudo extraer texto de todos los documentos descifrados con 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 no puede descifrar archivos PDF de Acrobat => 6.0

Este problema ha estado abierto con los propietarios del módulo, desde el 15 de septiembre de 2015 . No está claro en los comentarios relacionados con este problema cuándo los propietarios del proyecto solucionarán este problema. El último compromiso fue el 25 de junio de 2018.

Problemas de descifrado de PyPDF4

PyPDF4 es el reemplazo de PyPDF2. Este módulo también tiene problemas de descifrado con ciertos algoritmos utilizados para cifrar archivos PDF.

archivo de prueba: Adobe Acrobat 9.0 y posterior - nivel de cifrado AES de 256 bits

Mensaje de error de PyPDF2: solo se admiten los códigos de algoritmo 1 y 2

Mensaje de error de PyPDF4: solo se admiten los códigos de algoritmo 1 y 2. Este PDF usa el código 5


ACTUALIZACIÓN SECCIÓN 10-11-2019

Esta sección responde a sus actualizaciones del 10-07-2019 y 10-08-2019.

En su actualización, declaró que podía abrir un 'pdf seguro con Adobe Reader' e imprimir el documento en otro PDF, lo que elimina la bandera 'SEGURO'. Después de hacer algunas pruebas, creo que he descubierto lo que está ocurriendo en este escenario.

Adobe PDFs nivel de seguridad

Los archivos PDF de Adobe tienen varios tipos de controles de seguridad que el propietario del documento puede habilitar. Los controles pueden aplicarse con una contraseña o un certificado.

  1. Cifrado de documentos (forzado con una contraseña de documento abierto)

    • Cifrar todo el contenido del documento (más común)
    • Cifre todo el contenido del documento excepto metadatos => Acrobat 6.0
    • Cifrar solo archivos adjuntos => Acrobat 7.0
  2. Edición e impresión restrictivas (aplicadas con una contraseña de permisos)

    • Impresión permitida
    • Cambios permitidos

La imagen a continuación muestra un PDF de Adobe encriptado con encriptación AES de 256 bits. Para abrir o imprimir este PDF se requiere una contraseña. Cuando abra este documento en Adobe Reader con la contraseña, el título indicará SEGURO

password_level_encryption

Este documento requiere una contraseña para abrir con los módulos de Python que se mencionan en esta respuesta. Si intenta abrir un PDF encriptado con Adobe Reader. Deberías ver esto:

contraseña_prompt

Si no recibe esta advertencia, el documento no tiene habilitados los controles de seguridad o solo tiene habilitados los de edición e impresión restrictivos.

La imagen a continuación muestra la edición restrictiva habilitada con una contraseña en un documento PDF. La impresión de notas está habilitada . Para abrir o imprimir este PDF no se requiere una contraseña . Cuando abra este documento en Adobe Reader sin una contraseña, el título indicará SEGURO Esta es la misma advertencia que el PDF encriptado que se abrió con una contraseña.

Cuando imprime este documento en un nuevo PDF, se elimina la advertencia SEGURO porque se ha eliminado la edición restrictiva.

password_level_restrictive_editing

Todos los productos de Adobe aplican las restricciones establecidas por la contraseña de permisos. Sin embargo, si los productos de terceros no son compatibles con esta configuración, los destinatarios del documento pueden omitir algunas o todas las restricciones establecidas.

Así que supongo que el documento que está imprimiendo en PDF tiene habilitada la edición restrictiva y no tiene una contraseña requerida para abrir habilitada.

Sobre romper el cifrado de PDF

Ni PyPDF2 ni PyPDF4 están diseñados para romper la función de contraseña de apertura de documento de un documento PDF. Ambos módulos arrojarán el siguiente error si intentan abrir un archivo PDF protegido con contraseña cifrada.

PyPDF2.utils.PdfReadError: el archivo no ha sido descifrado

La función de contraseña de apertura de un archivo PDF encriptado se puede omitir utilizando una variedad de métodos, pero una sola técnica podría no funcionar y algunas no serán aceptables debido a varios factores, incluida la complejidad de la contraseña.

El cifrado de PDF funciona internamente con claves de cifrado de 40, 128 o 256 bits, según la versión de PDF. La clave de cifrado binario se deriva de una contraseña proporcionada por el usuario. La contraseña está sujeta a restricciones de longitud y codificación.

Por ejemplo, PDF 1.7 Adobe Extension Level 3 (Acrobat 9 - AES-256) introdujo caracteres Unicode (65.536 caracteres posibles) y aumentó la longitud máxima a 127 bytes en la representación UTF-8 de la contraseña.


El siguiente código abrirá un PDF con la edición restrictiva habilitada. Guardará este archivo en un nuevo PDF sin agregar la advertencia SEGURO. El código tika analizará el contenido del nuevo archivo.

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 si se requiere una contraseña para abrir el archivo. Este código se puede refinar y se pueden agregar otras funciones. Hay varias otras características que se pueden agregar, pero la documentación de pikepdf no coincide con los comentarios dentro de la base del código, por lo que se requiere más investigación para mejorar esto.

# 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)
La vida es compleja
fuente
2
¿Cómo está abriendo un archivo PDF seguro sin proporcionar una contraseña?
vida es compleja el
1
¿Se refiere solo a la protección de edición restrictiva?
vida es compleja el
1
Respuesta actualizada con código que funcionaba con un PDF que tenía habilitada la protección de edición restrictiva, pero permitía la impresión.
vida es compleja el
1
¿Puedes usar XHTML?
vida es compleja el
1
Modifiqué la respuesta a la salida XHTML. JSON es posible, pero requiere profundizar en el código del proyecto github relacionado con el analizador tika.
vida es compleja el
1

Puede intentar manejar el error que producen estos archivos cuando abre estos archivos sin contraseña.

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

Puede usar el pdf_obj devuelto para su trabajo de análisis. Además, puede proporcionar la contraseña en caso de que tenga un PDF encriptado.

Mahendra Singh
fuente
1
¡Gracias por su respuesta! Estamos tratando de leerlo sin contraseña. En este momento, pudimos hacerlo con el método que se explicó en mi ACTUALIZACIÓN 10/7/2019
Principiante
Esto está lejos de responder la pregunta. Parece que no has leído la pregunta completa.
shoonya ek
1
Esto maneja aquellos PDF protegidos donde normalmente falla pikepdf cuando el valor predeterminado de la contraseña es Ninguno. Al pasar una cadena vacía, puede abrir y analizar un documento PDF seguro correctamente (en los casos de prueba que ejecuté).
Mahendra Singh
1
@Beginner no tienes que convertir los PDF aquí en este caso. Esto es solo de mi experiencia previa que los archivos PDF seguros funcionan al proporcionar una contraseña vacía.
Mahendra Singh
1
@Beginner, este es mi código completo. Esto solo devuelve el pdf_object de pikepdf. En caso de que quiera guardar este pdf, simplemente guarde el objeto devuelto usando pdf_obj.save ('your_file_path'). Después de esto, puede usar este PDF para analizar texto y otros objetos. Yo uso una biblioteca llamada PdfPlumber para la extracción de texto.
Mahendra Singh
1

Para tabula-py, puede probar la opción de contraseña con read_pdf. Depende de la función de tabula-java, así que no estoy seguro de qué cifrado es compatible.

chezou
fuente