Cómo proteger PDFs con contraseña y cifrado en Python
Cifrar archivos PDF con contraseña garantiza que solo personas autorizadas puedan leerlos o modificarlos. Python ofrece tres herramientas principales: pypdf (nativo, sin dependencias externas), pikepdf (binding de qpdf, más potente) y la herramienta de línea de comandos qpdf. Esta guía cubre cifrado AES-256, control de permisos y eliminación de contraseñas.
Tipos de protección en PDF
| Tipo | Descripción |
|---|---|
| Contraseña de usuario | Se solicita al abrir el PDF (lector) |
| Contraseña de propietario | Controla permisos: imprimir, copiar, editar |
| Cifrado RC4-128 | Obsoleto, compatible con Acrobat 5+ |
| Cifrado AES-128 | Acrobat 7+, aceptable |
| Cifrado AES-256 | Acrobat X+, estándar recomendado |
Instalación
pip install pypdf pikepdf
# En Linux, pikepdf requiere:
apt-get install libqpdf-dev
Cifrar PDF con pypdf
from pypdf import PdfReader, PdfWriter
def cifrar_pdf(ruta_entrada, ruta_salida, contrasena_usuario, contrasena_propietario=None):
"""
Añade protección con contraseña a un PDF.
contrasena_propietario: si es None, se usa la misma que la de usuario
"""
reader = PdfReader(ruta_entrada)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
# Copiar metadatos
if reader.metadata:
writer.add_metadata(reader.metadata)
propietario = contrasena_propietario or contrasena_usuario
writer.encrypt(
user_password=contrasena_usuario,
owner_password=propietario,
use_128bit=False, # False = AES-256 en pypdf >= 3.x
permissions_flag=0 # 0 = sin permisos especiales para usuario
)
with open(ruta_salida, "wb") as f:
writer.write(f)
print(f"PDF cifrado guardado: {ruta_salida}")
# Proteger con contraseña de solo lectura
cifrar_pdf(
"informe_confidencial.pdf",
"informe_protegido.pdf",
contrasena_usuario="leer123",
contrasena_propietario="admin_propietario_XYZ"
)
Controlar permisos con pypdf
from pypdf import PdfReader, PdfWriter
from pypdf.constants import UserAccessPermissions
def cifrar_con_permisos(ruta_entrada, ruta_salida,
pwd_usuario, pwd_propietario,
permitir_imprimir=True,
permitir_copiar=False,
permitir_editar=False,
permitir_anotar=True):
"""Cifra con control granular de permisos."""
reader = PdfReader(ruta_entrada)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
# Construir máscara de permisos
permisos = 0
if permitir_imprimir:
permisos |= UserAccessPermissions.PRINT
permisos |= UserAccessPermissions.PRINT_HIGH_QUALITY
if permitir_copiar:
permisos |= UserAccessPermissions.EXTRACT
permisos |= UserAccessPermissions.EXTRACT_TEXT_AND_GRAPHICS
if permitir_editar:
permisos |= UserAccessPermissions.MODIFY
if permitir_anotar:
permisos |= UserAccessPermissions.ADD_OR_MODIFY
writer.encrypt(
user_password=pwd_usuario,
owner_password=pwd_propietario,
permissions_flag=permisos
)
with open(ruta_salida, "wb") as f:
writer.write(f)
print(f"Protegido con permisos: imprimir={permitir_imprimir}, copiar={permitir_copiar}, editar={permitir_editar}")
cifrar_con_permisos(
"contrato.pdf",
"contrato_protegido.pdf",
pwd_usuario="ver_contrato",
pwd_propietario="admin_firma_2026",
permitir_imprimir=True,
permitir_copiar=False,
permitir_editar=False
)
Cifrar con pikepdf (AES-256 nativo)
pikepdf ofrece cifrado AES-256 más robusto y compatible con PDF 2.0:
import pikepdf
def cifrar_pdf_pikepdf(ruta_entrada, ruta_salida,
pwd_usuario, pwd_propietario=None):
"""Cifra con AES-256 usando pikepdf."""
propietario = pwd_propietario or pwd_usuario
with pikepdf.open(ruta_entrada) as pdf:
permisos = pikepdf.Permissions(
accessibility=True,
extract=False, # No copiar texto
modify_annotation=True,
modify_assembly=False,
modify_form=False,
modify_other=False,
print_lowres=True,
print_highres=True,
)
encriptacion = pikepdf.Encryption(
owner=propietario,
user=pwd_usuario,
R=6, # R=6 → AES-256 (PDF 2.0)
allow=permisos
)
pdf.save(ruta_salida, encryption=encriptacion)
print(f"Guardado con AES-256: {ruta_salida}")
cifrar_pdf_pikepdf(
"documento.pdf",
"documento_aes256.pdf",
pwd_usuario="acceso_seguro_2026",
pwd_propietario="propietario_master_XYZ"
)
Eliminar contraseña de un PDF
Si conoces la contraseña, puedes crear una versión sin cifrar:
from pypdf import PdfReader, PdfWriter
def eliminar_contrasena(ruta_entrada, contrasena, ruta_salida):
reader = PdfReader(ruta_entrada)
if reader.is_encrypted:
resultado = reader.decrypt(contrasena)
if resultado == 0:
print("ERROR: Contraseña incorrecta")
return False
print(f"PDF desprotegido (permisos: {resultado})")
else:
print("El PDF no está cifrado")
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
with open(ruta_salida, "wb") as f:
writer.write(f)
print(f"PDF sin contraseña: {ruta_salida}")
return True
eliminar_contrasena("documento_protegido.pdf", "mi_contrasena", "documento_libre.pdf")
Usando pikepdf para descifrar
import pikepdf
def descifrar_pdf_pikepdf(ruta_entrada, contrasena, ruta_salida):
try:
with pikepdf.open(ruta_entrada, password=contrasena) as pdf:
# Guardar sin cifrado
pdf.save(ruta_salida)
print(f"Descifrado correctamente: {ruta_salida}")
return True
except pikepdf.PasswordError:
print("ERROR: Contraseña incorrecta")
return False
except Exception as e:
print(f"ERROR: {e}")
return False
descifrar_pdf_pikepdf("protegido.pdf", "mi_clave", "descifrado.pdf")
Verificar estado de cifrado
from pypdf import PdfReader
import pikepdf
def inspeccionar_pdf(ruta):
reader = PdfReader(ruta)
print(f"Archivo: {ruta}")
print(f"Cifrado: {reader.is_encrypted}")
if reader.is_encrypted:
# Intentar con contraseña vacía (propietario sin restricciones)
nivel = reader.decrypt("")
if nivel > 0:
print(f" Accesible sin contraseña de usuario (nivel: {nivel})")
else:
print(f" Requiere contraseña de usuario")
meta = reader.metadata
if meta:
print(f"Páginas: {len(reader.pages)}")
print(f"Título: {meta.get('/Title', 'N/A')}")
print(f"Autor: {meta.get('/Author', 'N/A')}")
print(f"Creador: {meta.get('/Creator', 'N/A')}")
inspeccionar_pdf("documento_misterioso.pdf")
Cifrado por lotes
from pypdf import PdfReader, PdfWriter
from pathlib import Path
import secrets
import json
def batch_cifrar(carpeta_entrada, carpeta_salida,
contrasena_usuario, contrasena_propietario=None):
carpeta_entrada = Path(carpeta_entrada)
carpeta_salida = Path(carpeta_salida)
carpeta_salida.mkdir(exist_ok=True)
pdfs = list(carpeta_entrada.glob("*.pdf"))
print(f"Cifrando {len(pdfs)} PDFs...")
registro = {}
for pdf_path in pdfs:
salida = carpeta_salida / pdf_path.name
try:
reader = PdfReader(str(pdf_path))
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
writer.encrypt(contrasena_usuario, contrasena_propietario)
with open(salida, "wb") as f:
writer.write(f)
registro[pdf_path.name] = "OK"
print(f" ✓ {pdf_path.name}")
except Exception as e:
registro[pdf_path.name] = f"ERROR: {e}"
print(f" ✗ {pdf_path.name}: {e}")
# Guardar registro
with open(carpeta_salida / "cifrado_registro.json", "w") as f:
json.dump(registro, f, indent=2)
exito = sum(1 for v in registro.values() if v == "OK")
print(f"\nCompletado: {exito}/{len(pdfs)} PDFs cifrados")
batch_cifrar("documentos/", "documentos_cifrados/", "password_empresa_2026")
Añadir marca de agua de seguridad
from pypdf import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
import io
import math
def crear_marca_agua(texto="CONFIDENCIAL", opacidad=0.15):
"""Crea una página de marca de agua diagonal."""
packet = io.BytesIO()
c = canvas.Canvas(packet, pagesize=A4)
ancho, alto = A4
c.saveState()
c.setFillColorRGB(0.8, 0, 0, opacidad)
c.setFont("Helvetica-Bold", 60)
# Rotar 45 grados y centrar
c.translate(ancho / 2, alto / 2)
c.rotate(45)
anchura_texto = c.stringWidth(texto, "Helvetica-Bold", 60)
c.drawString(-anchura_texto / 2, 0, texto)
c.restoreState()
c.save()
packet.seek(0)
return PdfReader(packet)
def aplicar_marca_agua(ruta_entrada, ruta_salida, texto="CONFIDENCIAL"):
marca_agua = crear_marca_agua(texto)
reader = PdfReader(ruta_entrada)
writer = PdfWriter()
for page in reader.pages:
page.merge_page(marca_agua.pages[0])
writer.add_page(page)
# Cifrar además con contraseña
writer.encrypt("ver_solo", "admin_propietario")
with open(ruta_salida, "wb") as f:
writer.write(f)
print(f"Documento con marca de agua y cifrado: {ruta_salida}")
aplicar_marca_agua("informe.pdf", "informe_confidencial.pdf", "SOLO LECTURA")
Recurso adicional
Para convertir PDFs a Word, imagen, texto u otros formatos sin necesidad de programar, usa KaijuConverter — gratis y sin registro.
Conversiones relacionadas
Conversiones de documento que siguen este tema: