CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pikepdf

Read and write PDFs with Python, powered by qpdf

Pending
Overview
Eval results
Files

encryption.mddocs/

Encryption and Security

PDF encryption, decryption, password handling, and permission management for document security. These capabilities enable comprehensive control over PDF access and usage restrictions.

Capabilities

Encryption Class

Comprehensive PDF encryption configuration for protecting documents with passwords and permissions.

class Encryption:
    """
    PDF encryption settings for document security.
    
    Configures password protection and usage permissions for PDF documents.
    Supports standard security handlers with various encryption strengths.
    """
    
    def __init__(self, *, owner: str = '', user: str = '', R: int = 6, 
                 allow: Permissions = None, aes: bool = True, 
                 metadata: bool = True) -> None:
        """
        Create encryption settings for a PDF.
        
        Parameters:
        - owner (str): Owner password (full permissions when provided)
        - user (str): User password (restricted permissions when provided)
        - R (int): Security handler revision (2, 3, 4, or 6)
                  Higher numbers provide stronger encryption
        - allow (Permissions): Permitted operations for users
        - aes (bool): Use AES encryption (recommended, requires R >= 4)
        - metadata (bool): Encrypt document metadata
        
        Notes:
        - R=2: 40-bit RC4 encryption (weak, legacy only)
        - R=3: 128-bit RC4 encryption
        - R=4: 128-bit RC4 or AES encryption
        - R=6: 256-bit AES encryption (strongest, recommended)
        """
    
    @property
    def user_password(self) -> str:
        """
        User password for restricted access.
        
        Returns:
        str: User password string
        """
    
    @property
    def owner_password(self) -> str:
        """
        Owner password for full access.
        
        Returns:
        str: Owner password string
        """
    
    @property
    def strength(self) -> int:
        """
        Encryption strength indicator.
        
        Returns:
        int: Security handler revision number (2, 3, 4, or 6)
        """
    
    @property
    def permissions(self) -> Permissions:
        """
        Permitted operations under user password.
        
        Returns:
        Permissions: Object specifying allowed operations
        """

Permissions Class

Fine-grained control over document usage permissions and restrictions.

class Permissions:
    """
    PDF permission flags controlling document usage restrictions.
    
    Defines what operations users can perform when accessing
    a PDF with the user password (as opposed to owner password).
    """
    
    def __init__(self, *, accessibility: bool = True, assemble: bool = True, 
                 extract: bool = True, modify_annotation: bool = True, 
                 modify_assembly: bool = True, modify_form: bool = True, 
                 modify_other: bool = True, print_lowres: bool = True, 
                 print_highres: bool = True) -> None:
        """
        Create a permissions object with specified restrictions.
        
        Parameters:
        - accessibility (bool): Allow text extraction for accessibility
        - assemble (bool): Allow document assembly (insert, delete, rotate pages)
        - extract (bool): Allow text and graphics extraction  
        - modify_annotation (bool): Allow annotation modification
        - modify_assembly (bool): Allow page assembly operations
        - modify_form (bool): Allow form field filling and signing
        - modify_other (bool): Allow other document modifications
        - print_lowres (bool): Allow low resolution printing
        - print_highres (bool): Allow high resolution printing
        """
    
    @property
    def accessibility(self) -> bool:
        """
        Permission to extract text for accessibility purposes.
        
        Even with extraction disabled, this allows screen readers
        and other accessibility tools to access document content.
        
        Returns:
        bool: True if accessibility extraction is permitted
        """
    
    @accessibility.setter
    def accessibility(self, value: bool) -> None:
        """Set accessibility extraction permission."""
    
    @property
    def assemble(self) -> bool:
        """
        Permission to assemble the document.
        
        Controls operations like inserting, deleting, and rotating pages.
        
        Returns:
        bool: True if document assembly is permitted
        """
    
    @assemble.setter
    def assemble(self, value: bool) -> None:
        """Set document assembly permission."""
    
    @property
    def extract(self) -> bool:
        """
        Permission to extract text and graphics.
        
        Controls copying text and images from the document.
        
        Returns:
        bool: True if content extraction is permitted
        """
    
    @extract.setter
    def extract(self, value: bool) -> None:
        """Set content extraction permission."""
    
    @property
    def modify_annotation(self) -> bool:
        """
        Permission to modify annotations.
        
        Controls adding, editing, and deleting annotations and form fields.
        
        Returns:
        bool: True if annotation modification is permitted
        """
    
    @modify_annotation.setter
    def modify_annotation(self, value: bool) -> None:
        """Set annotation modification permission."""
    
    @property
    def modify_assembly(self) -> bool:
        """
        Permission to modify document assembly.
        
        Controls page-level operations and document structure changes.
        
        Returns:
        bool: True if assembly modification is permitted
        """
    
    @modify_assembly.setter
    def modify_assembly(self, value: bool) -> None:
        """Set assembly modification permission."""
    
    @property
    def modify_form(self) -> bool:
        """
        Permission to fill and sign form fields.
        
        Controls form field interaction and digital signatures.
        
        Returns:
        bool: True if form modification is permitted
        """
    
    @modify_form.setter
    def modify_form(self, value: bool) -> None:
        """Set form modification permission."""
    
    @property
    def modify_other(self) -> bool:
        """
        Permission for other document modifications.
        
        General permission for content editing and modification operations.
        
        Returns:
        bool: True if other modifications are permitted
        """
    
    @modify_other.setter
    def modify_other(self, value: bool) -> None:
        """Set other modifications permission."""
    
    @property
    def print_lowres(self) -> bool:
        """
        Permission to print at low resolution.
        
        Typically allows printing at 150 DPI or lower resolution.
        
        Returns:
        bool: True if low resolution printing is permitted
        """
    
    @print_lowres.setter
    def print_lowres(self, value: bool) -> None:
        """Set low resolution printing permission."""
    
    @property
    def print_highres(self) -> bool:
        """
        Permission to print at high resolution.
        
        Allows printing at full resolution without restrictions.
        
        Returns:
        bool: True if high resolution printing is permitted
        """
    
    @print_highres.setter
    def print_highres(self, value: bool) -> None:
        """Set high resolution printing permission."""

EncryptionInfo Class

Information about existing PDF encryption for analysis and modification.

class EncryptionInfo:
    """
    Information about PDF encryption status and parameters.
    
    Provides details about existing encryption settings
    for analysis and conditional processing.
    """
    
    @property
    def owner_password_matched(self) -> bool:
        """
        Whether the owner password was correctly provided.
        
        Returns:
        bool: True if owner password grants full access
        """
    
    @property
    def user_password_matched(self) -> bool:
        """
        Whether the user password was correctly provided.
        
        Returns:
        bool: True if user password was used for access
        """
    
    @property
    def bits(self) -> int:
        """
        Encryption strength in bits.
        
        Returns:
        int: Encryption key length (40, 128, or 256 bits)
        """
    
    @property
    def method(self) -> str:
        """
        Encryption method used.
        
        Returns:
        str: Encryption algorithm ('RC4', 'AES', or 'Unknown')
        """
    
    @property
    def permissions(self) -> Permissions:
        """
        Current permission settings.
        
        Returns:
        Permissions: Active permissions for user access
        """

Password-related Exceptions

Specialized exceptions for password and encryption handling.

class PasswordError(PdfError):
    """
    Raised when PDF password operations fail.
    
    This occurs when:
    - No password is provided for an encrypted PDF
    - An incorrect password is provided
    - Password format is invalid
    """

class DataDecodingError(PdfError):
    """
    Raised when encrypted data cannot be decoded.
    
    This can occur with:
    - Corrupted encryption data
    - Unsupported encryption methods
    - Invalid encryption parameters
    """

Usage Examples

Creating Encrypted PDFs

import pikepdf

# Create a new PDF
pdf = pikepdf.new()
pdf.add_blank_page(page_size=(612, 792))

# Add some content
content = """
BT
/F1 12 Tf
100 700 Td
(This is a protected document) Tj
ET
"""
content_stream = pikepdf.Stream(pdf, content.encode())
pdf.pages[0]['/Contents'] = content_stream

# Configure encryption with strong settings
encryption = pikepdf.Encryption(
    owner='secret_owner_password',
    user='user_password',
    R=6,  # Use 256-bit AES encryption
    aes=True,
    metadata=True  # Encrypt metadata too
)

# Configure restrictive permissions
permissions = pikepdf.Permissions(
    accessibility=True,  # Always allow accessibility
    extract=False,       # Prevent text copying
    print_lowres=True,   # Allow low-res printing only
    print_highres=False, # Disable high-res printing
    modify_annotation=False,  # Prevent annotation changes
    modify_other=False   # Prevent content modification
)

encryption.permissions = permissions

# Save with encryption
pdf.save('protected_document.pdf', encryption=encryption)
pdf.close()

print("Created encrypted PDF with strong protection")

Opening Encrypted PDFs

import pikepdf

# Try to open encrypted PDF
try:
    # First attempt - no password (will fail if encrypted)
    pdf = pikepdf.open('protected_document.pdf')
    print("PDF is not encrypted or no password required")
    
except pikepdf.PasswordError:
    print("PDF requires a password")
    
    # Try with user password
    try:
        pdf = pikepdf.open('protected_document.pdf', password='user_password')
        print("Opened with user password - restricted access")
        
        # Check if we have owner access
        if pdf.encryption_info.owner_password_matched:
            print("Owner password access - full permissions")
        else:
            print("User password access - limited permissions")
            
    except pikepdf.PasswordError:
        print("User password incorrect, trying owner password")
        
        try:
            pdf = pikepdf.open('protected_document.pdf', password='secret_owner_password')
            print("Opened with owner password - full access")
            
        except pikepdf.PasswordError:
            print("Owner password also incorrect - cannot open PDF")
            pdf = None

if pdf:
    # Analyze encryption settings
    enc_info = pdf.encryption_info
    print(f"Encryption method: {enc_info.method}")
    print(f"Encryption strength: {enc_info.bits} bits")
    print(f"Owner access: {enc_info.owner_password_matched}")
    print(f"User access: {enc_info.user_password_matched}")
    
    # Check permissions
    perms = enc_info.permissions
    print(f"Can extract text: {perms.extract}")
    print(f"Can print high-res: {perms.print_highres}")
    print(f"Can modify: {perms.modify_other}")
    
    pdf.close()

Password Protection Levels

import pikepdf

def create_protection_levels():
    """Demonstrate different levels of PDF protection."""
    
    # Level 1: Basic password protection, full permissions
    pdf1 = pikepdf.new()
    pdf1.add_blank_page()
    
    basic_encryption = pikepdf.Encryption(
        user='simple123',
        owner='admin123',
        R=4,  # 128-bit encryption
        aes=True
    )
    # Default permissions allow everything
    
    pdf1.save('basic_protected.pdf', encryption=basic_encryption)
    pdf1.close()
    print("Created: basic_protected.pdf (password required, full permissions)")
    
    # Level 2: Moderate protection with some restrictions
    pdf2 = pikepdf.new()
    pdf2.add_blank_page()
    
    moderate_permissions = pikepdf.Permissions(
        extract=False,        # No copying
        print_highres=False,  # Low-res printing only
        modify_other=False    # No content modification
    )
    
    moderate_encryption = pikepdf.Encryption(
        user='user456',
        owner='owner456',
        R=6,  # 256-bit AES
        allow=moderate_permissions
    )
    
    pdf2.save('moderate_protected.pdf', encryption=moderate_encryption)
    pdf2.close()
    print("Created: moderate_protected.pdf (restricted copying/printing)")
    
    # Level 3: High security with minimal permissions
    pdf3 = pikepdf.new()
    pdf3.add_blank_page()
    
    strict_permissions = pikepdf.Permissions(
        accessibility=True,      # Keep accessibility
        extract=False,           # No copying
        print_lowres=False,      # No printing at all
        print_highres=False,
        modify_annotation=False, # No annotations
        modify_form=False,       # No form filling
        modify_other=False,      # No modifications
        assemble=False           # No page operations
    )
    
    strict_encryption = pikepdf.Encryption(
        user='readonly789',
        owner='superadmin789',
        R=6,  # Strongest encryption
        allow=strict_permissions,
        metadata=True  # Encrypt metadata too
    )
    
    pdf3.save('strict_protected.pdf', encryption=strict_encryption)
    pdf3.close()
    print("Created: strict_protected.pdf (view-only, no operations allowed)")

create_protection_levels()

Removing Encryption

import pikepdf

def remove_encryption(input_file, password, output_file):
    """Remove encryption from a PDF if password is known."""
    
    try:
        # Open encrypted PDF
        pdf = pikepdf.open(input_file, password=password)
        
        # Check if we have owner access (required to remove encryption)
        if not pdf.encryption_info.owner_password_matched:
            print("Warning: Only user access - encryption removal may not work")
        
        # Save without encryption (no encryption parameter)
        pdf.save(output_file)
        pdf.close()
        
        print(f"Successfully removed encryption: {input_file} -> {output_file}")
        
        # Verify the result
        verify_pdf = pikepdf.open(output_file)
        print(f"Verification: New PDF has {len(verify_pdf.pages)} pages")
        print(f"Verification: PDF is encrypted: {verify_pdf.is_encrypted}")
        verify_pdf.close()
        
    except pikepdf.PasswordError:
        print(f"Error: Incorrect password for {input_file}")
    except Exception as e:
        print(f"Error removing encryption: {e}")

# Remove encryption from various protection levels
remove_encryption('basic_protected.pdf', 'admin123', 'basic_unprotected.pdf')
remove_encryption('moderate_protected.pdf', 'owner456', 'moderate_unprotected.pdf')
remove_encryption('strict_protected.pdf', 'superadmin789', 'strict_unprotected.pdf')

Changing Encryption Settings

import pikepdf

def upgrade_encryption(input_file, old_password, new_encryption, output_file):
    """Upgrade encryption settings for an existing PDF."""
    
    try:
        # Open with existing password
        pdf = pikepdf.open(input_file, password=old_password)
        
        print(f"Current encryption: {pdf.encryption_info.method}, "
              f"{pdf.encryption_info.bits} bits")
        
        # Save with new encryption settings
        pdf.save(output_file, encryption=new_encryption)
        pdf.close()
        
        print(f"Upgraded encryption: {input_file} -> {output_file}")
        
        # Verify new encryption
        verify_pdf = pikepdf.open(output_file, password=new_encryption.owner_password)
        new_info = verify_pdf.encryption_info
        print(f"New encryption: {new_info.method}, {new_info.bits} bits")
        verify_pdf.close()
        
    except Exception as e:
        print(f"Error upgrading encryption: {e}")

# Upgrade to strongest encryption
new_encryption = pikepdf.Encryption(
    owner='new_super_secure_password_2024',
    user='new_user_password_2024',
    R=6,  # 256-bit AES
    aes=True,
    metadata=True,
    allow=pikepdf.Permissions(
        extract=False,
        print_highres=True,  # Allow high-quality printing
        modify_other=False
    )
)

upgrade_encryption('basic_protected.pdf', 'admin123', new_encryption, 'upgraded_protected.pdf')

Bulk Encryption Operations

import pikepdf
import os
from pathlib import Path

def encrypt_directory(directory_path, encryption_settings, password):
    """Encrypt all PDFs in a directory."""
    
    directory = Path(directory_path)
    encrypted_dir = directory / 'encrypted'
    encrypted_dir.mkdir(exist_ok=True)
    
    pdf_files = list(directory.glob('*.pdf'))
    results = {'success': [], 'failed': []}
    
    for pdf_file in pdf_files:
        try:
            # Skip already encrypted files (basic check)
            try:
                test_pdf = pikepdf.open(pdf_file)
                if test_pdf.is_encrypted:
                    print(f"Skipping already encrypted: {pdf_file.name}")
                    test_pdf.close()
                    continue
                test_pdf.close()
            except pikepdf.PasswordError:
                print(f"Skipping password-protected: {pdf_file.name}")
                continue
            
            # Encrypt the PDF
            pdf = pikepdf.open(pdf_file)
            output_path = encrypted_dir / pdf_file.name
            pdf.save(output_path, encryption=encryption_settings)
            pdf.close()
            
            results['success'].append(pdf_file.name)
            print(f"Encrypted: {pdf_file.name}")
            
        except Exception as e:
            results['failed'].append((pdf_file.name, str(e)))
            print(f"Failed to encrypt {pdf_file.name}: {e}")
    
    print(f"\nEncryption complete:")
    print(f"  Successful: {len(results['success'])} files")
    print(f"  Failed: {len(results['failed'])} files")
    
    return results

# Bulk encryption with standard settings
bulk_encryption = pikepdf.Encryption(
    owner='bulk_owner_2024',
    user='bulk_user_2024',
    R=6,
    allow=pikepdf.Permissions(
        print_highres=True,
        extract=False,
        modify_other=False
    )
)

# Encrypt all PDFs in current directory
# results = encrypt_directory('.', bulk_encryption, 'bulk_user_2024')

Security Analysis Tool

import pikepdf
from pathlib import Path

def analyze_pdf_security(pdf_path, password=None):
    """Analyze the security settings of a PDF file."""
    
    try:
        # Try to open without password first
        try:
            pdf = pikepdf.open(pdf_path)
            encrypted = False
        except pikepdf.PasswordError:
            if password:
                pdf = pikepdf.open(pdf_path, password=password)
                encrypted = True
            else:
                return {"error": "PDF is encrypted but no password provided"}
        
        analysis = {
            "file": str(pdf_path),
            "encrypted": encrypted,
            "pages": len(pdf.pages),
            "pdf_version": pdf.pdf_version
        }
        
        if encrypted:
            enc_info = pdf.encryption_info
            analysis.update({
                "encryption_method": enc_info.method,
                "encryption_bits": enc_info.bits,
                "owner_access": enc_info.owner_password_matched,
                "user_access": enc_info.user_password_matched,
                "permissions": {
                    "extract_text": enc_info.permissions.extract,
                    "print_lowres": enc_info.permissions.print_lowres,
                    "print_highres": enc_info.permissions.print_highres,
                    "modify_annotations": enc_info.permissions.modify_annotation,
                    "modify_forms": enc_info.permissions.modify_form,
                    "modify_other": enc_info.permissions.modify_other,
                    "assemble_document": enc_info.permissions.assemble,
                    "accessibility": enc_info.permissions.accessibility
                }
            })
        
        pdf.close()
        return analysis
        
    except Exception as e:
        return {"error": str(e)}

def security_report(directory_path):
    """Generate a security report for all PDFs in a directory."""
    
    directory = Path(directory_path)
    pdf_files = list(directory.glob('*.pdf'))
    
    print(f"PDF Security Report for: {directory}")
    print("=" * 60)
    
    encrypted_count = 0
    unencrypted_count = 0
    error_count = 0
    
    for pdf_file in pdf_files:
        analysis = analyze_pdf_security(pdf_file)
        
        if "error" in analysis:
            print(f"\n❌ {pdf_file.name}: {analysis['error']}")
            error_count += 1
        elif analysis["encrypted"]:
            print(f"\n🔒 {pdf_file.name}: ENCRYPTED")
            print(f"   Method: {analysis['encryption_method']}")
            print(f"   Strength: {analysis['encryption_bits']} bits")
            print(f"   Access Level: {'Owner' if analysis['owner_access'] else 'User'}")
            
            # Show key restrictions
            perms = analysis["permissions"]
            restrictions = []
            if not perms["extract_text"]: restrictions.append("No copying")
            if not perms["print_highres"]: restrictions.append("No high-res printing")  
            if not perms["modify_other"]: restrictions.append("No modification")
            
            if restrictions:
                print(f"   Restrictions: {', '.join(restrictions)}")
            else:
                print("   Restrictions: None")
                
            encrypted_count += 1
        else:
            print(f"\n🔓 {pdf_file.name}: UNPROTECTED")
            unencrypted_count += 1
    
    print(f"\n" + "=" * 60)
    print(f"Summary: {len(pdf_files)} PDFs analyzed")
    print(f"  Encrypted: {encrypted_count}")
    print(f"  Unprotected: {unencrypted_count}")
    print(f"  Errors: {error_count}")

# Generate security report for current directory
# security_report('.')

Install with Tessl CLI

npx tessl i tessl/pypi-pikepdf

docs

advanced.md

attachments.md

content-streams.md

core-operations.md

encryption.md

forms.md

images.md

index.md

metadata.md

objects.md

outlines.md

pages.md

tile.json