or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced.mdattachments.mdcontent-streams.mdcore-operations.mdencryption.mdforms.mdimages.mdindex.mdmetadata.mdobjects.mdoutlines.mdpages.md

encryption.mddocs/

0

# Encryption and Security

1

2

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

3

4

## Capabilities

5

6

### Encryption Class

7

8

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

9

10

```python { .api }

11

class Encryption:

12

"""

13

PDF encryption settings for document security.

14

15

Configures password protection and usage permissions for PDF documents.

16

Supports standard security handlers with various encryption strengths.

17

"""

18

19

def __init__(self, *, owner: str = '', user: str = '', R: int = 6,

20

allow: Permissions = None, aes: bool = True,

21

metadata: bool = True) -> None:

22

"""

23

Create encryption settings for a PDF.

24

25

Parameters:

26

- owner (str): Owner password (full permissions when provided)

27

- user (str): User password (restricted permissions when provided)

28

- R (int): Security handler revision (2, 3, 4, or 6)

29

Higher numbers provide stronger encryption

30

- allow (Permissions): Permitted operations for users

31

- aes (bool): Use AES encryption (recommended, requires R >= 4)

32

- metadata (bool): Encrypt document metadata

33

34

Notes:

35

- R=2: 40-bit RC4 encryption (weak, legacy only)

36

- R=3: 128-bit RC4 encryption

37

- R=4: 128-bit RC4 or AES encryption

38

- R=6: 256-bit AES encryption (strongest, recommended)

39

"""

40

41

@property

42

def user_password(self) -> str:

43

"""

44

User password for restricted access.

45

46

Returns:

47

str: User password string

48

"""

49

50

@property

51

def owner_password(self) -> str:

52

"""

53

Owner password for full access.

54

55

Returns:

56

str: Owner password string

57

"""

58

59

@property

60

def strength(self) -> int:

61

"""

62

Encryption strength indicator.

63

64

Returns:

65

int: Security handler revision number (2, 3, 4, or 6)

66

"""

67

68

@property

69

def permissions(self) -> Permissions:

70

"""

71

Permitted operations under user password.

72

73

Returns:

74

Permissions: Object specifying allowed operations

75

"""

76

```

77

78

### Permissions Class

79

80

Fine-grained control over document usage permissions and restrictions.

81

82

```python { .api }

83

class Permissions:

84

"""

85

PDF permission flags controlling document usage restrictions.

86

87

Defines what operations users can perform when accessing

88

a PDF with the user password (as opposed to owner password).

89

"""

90

91

def __init__(self, *, accessibility: bool = True, assemble: bool = True,

92

extract: bool = True, modify_annotation: bool = True,

93

modify_assembly: bool = True, modify_form: bool = True,

94

modify_other: bool = True, print_lowres: bool = True,

95

print_highres: bool = True) -> None:

96

"""

97

Create a permissions object with specified restrictions.

98

99

Parameters:

100

- accessibility (bool): Allow text extraction for accessibility

101

- assemble (bool): Allow document assembly (insert, delete, rotate pages)

102

- extract (bool): Allow text and graphics extraction

103

- modify_annotation (bool): Allow annotation modification

104

- modify_assembly (bool): Allow page assembly operations

105

- modify_form (bool): Allow form field filling and signing

106

- modify_other (bool): Allow other document modifications

107

- print_lowres (bool): Allow low resolution printing

108

- print_highres (bool): Allow high resolution printing

109

"""

110

111

@property

112

def accessibility(self) -> bool:

113

"""

114

Permission to extract text for accessibility purposes.

115

116

Even with extraction disabled, this allows screen readers

117

and other accessibility tools to access document content.

118

119

Returns:

120

bool: True if accessibility extraction is permitted

121

"""

122

123

@accessibility.setter

124

def accessibility(self, value: bool) -> None:

125

"""Set accessibility extraction permission."""

126

127

@property

128

def assemble(self) -> bool:

129

"""

130

Permission to assemble the document.

131

132

Controls operations like inserting, deleting, and rotating pages.

133

134

Returns:

135

bool: True if document assembly is permitted

136

"""

137

138

@assemble.setter

139

def assemble(self, value: bool) -> None:

140

"""Set document assembly permission."""

141

142

@property

143

def extract(self) -> bool:

144

"""

145

Permission to extract text and graphics.

146

147

Controls copying text and images from the document.

148

149

Returns:

150

bool: True if content extraction is permitted

151

"""

152

153

@extract.setter

154

def extract(self, value: bool) -> None:

155

"""Set content extraction permission."""

156

157

@property

158

def modify_annotation(self) -> bool:

159

"""

160

Permission to modify annotations.

161

162

Controls adding, editing, and deleting annotations and form fields.

163

164

Returns:

165

bool: True if annotation modification is permitted

166

"""

167

168

@modify_annotation.setter

169

def modify_annotation(self, value: bool) -> None:

170

"""Set annotation modification permission."""

171

172

@property

173

def modify_assembly(self) -> bool:

174

"""

175

Permission to modify document assembly.

176

177

Controls page-level operations and document structure changes.

178

179

Returns:

180

bool: True if assembly modification is permitted

181

"""

182

183

@modify_assembly.setter

184

def modify_assembly(self, value: bool) -> None:

185

"""Set assembly modification permission."""

186

187

@property

188

def modify_form(self) -> bool:

189

"""

190

Permission to fill and sign form fields.

191

192

Controls form field interaction and digital signatures.

193

194

Returns:

195

bool: True if form modification is permitted

196

"""

197

198

@modify_form.setter

199

def modify_form(self, value: bool) -> None:

200

"""Set form modification permission."""

201

202

@property

203

def modify_other(self) -> bool:

204

"""

205

Permission for other document modifications.

206

207

General permission for content editing and modification operations.

208

209

Returns:

210

bool: True if other modifications are permitted

211

"""

212

213

@modify_other.setter

214

def modify_other(self, value: bool) -> None:

215

"""Set other modifications permission."""

216

217

@property

218

def print_lowres(self) -> bool:

219

"""

220

Permission to print at low resolution.

221

222

Typically allows printing at 150 DPI or lower resolution.

223

224

Returns:

225

bool: True if low resolution printing is permitted

226

"""

227

228

@print_lowres.setter

229

def print_lowres(self, value: bool) -> None:

230

"""Set low resolution printing permission."""

231

232

@property

233

def print_highres(self) -> bool:

234

"""

235

Permission to print at high resolution.

236

237

Allows printing at full resolution without restrictions.

238

239

Returns:

240

bool: True if high resolution printing is permitted

241

"""

242

243

@print_highres.setter

244

def print_highres(self, value: bool) -> None:

245

"""Set high resolution printing permission."""

246

```

247

248

### EncryptionInfo Class

249

250

Information about existing PDF encryption for analysis and modification.

251

252

```python { .api }

253

class EncryptionInfo:

254

"""

255

Information about PDF encryption status and parameters.

256

257

Provides details about existing encryption settings

258

for analysis and conditional processing.

259

"""

260

261

@property

262

def owner_password_matched(self) -> bool:

263

"""

264

Whether the owner password was correctly provided.

265

266

Returns:

267

bool: True if owner password grants full access

268

"""

269

270

@property

271

def user_password_matched(self) -> bool:

272

"""

273

Whether the user password was correctly provided.

274

275

Returns:

276

bool: True if user password was used for access

277

"""

278

279

@property

280

def bits(self) -> int:

281

"""

282

Encryption strength in bits.

283

284

Returns:

285

int: Encryption key length (40, 128, or 256 bits)

286

"""

287

288

@property

289

def method(self) -> str:

290

"""

291

Encryption method used.

292

293

Returns:

294

str: Encryption algorithm ('RC4', 'AES', or 'Unknown')

295

"""

296

297

@property

298

def permissions(self) -> Permissions:

299

"""

300

Current permission settings.

301

302

Returns:

303

Permissions: Active permissions for user access

304

"""

305

```

306

307

### Password-related Exceptions

308

309

Specialized exceptions for password and encryption handling.

310

311

```python { .api }

312

class PasswordError(PdfError):

313

"""

314

Raised when PDF password operations fail.

315

316

This occurs when:

317

- No password is provided for an encrypted PDF

318

- An incorrect password is provided

319

- Password format is invalid

320

"""

321

322

class DataDecodingError(PdfError):

323

"""

324

Raised when encrypted data cannot be decoded.

325

326

This can occur with:

327

- Corrupted encryption data

328

- Unsupported encryption methods

329

- Invalid encryption parameters

330

"""

331

```

332

333

## Usage Examples

334

335

### Creating Encrypted PDFs

336

337

```python

338

import pikepdf

339

340

# Create a new PDF

341

pdf = pikepdf.new()

342

pdf.add_blank_page(page_size=(612, 792))

343

344

# Add some content

345

content = """

346

BT

347

/F1 12 Tf

348

100 700 Td

349

(This is a protected document) Tj

350

ET

351

"""

352

content_stream = pikepdf.Stream(pdf, content.encode())

353

pdf.pages[0]['/Contents'] = content_stream

354

355

# Configure encryption with strong settings

356

encryption = pikepdf.Encryption(

357

owner='secret_owner_password',

358

user='user_password',

359

R=6, # Use 256-bit AES encryption

360

aes=True,

361

metadata=True # Encrypt metadata too

362

)

363

364

# Configure restrictive permissions

365

permissions = pikepdf.Permissions(

366

accessibility=True, # Always allow accessibility

367

extract=False, # Prevent text copying

368

print_lowres=True, # Allow low-res printing only

369

print_highres=False, # Disable high-res printing

370

modify_annotation=False, # Prevent annotation changes

371

modify_other=False # Prevent content modification

372

)

373

374

encryption.permissions = permissions

375

376

# Save with encryption

377

pdf.save('protected_document.pdf', encryption=encryption)

378

pdf.close()

379

380

print("Created encrypted PDF with strong protection")

381

```

382

383

### Opening Encrypted PDFs

384

385

```python

386

import pikepdf

387

388

# Try to open encrypted PDF

389

try:

390

# First attempt - no password (will fail if encrypted)

391

pdf = pikepdf.open('protected_document.pdf')

392

print("PDF is not encrypted or no password required")

393

394

except pikepdf.PasswordError:

395

print("PDF requires a password")

396

397

# Try with user password

398

try:

399

pdf = pikepdf.open('protected_document.pdf', password='user_password')

400

print("Opened with user password - restricted access")

401

402

# Check if we have owner access

403

if pdf.encryption_info.owner_password_matched:

404

print("Owner password access - full permissions")

405

else:

406

print("User password access - limited permissions")

407

408

except pikepdf.PasswordError:

409

print("User password incorrect, trying owner password")

410

411

try:

412

pdf = pikepdf.open('protected_document.pdf', password='secret_owner_password')

413

print("Opened with owner password - full access")

414

415

except pikepdf.PasswordError:

416

print("Owner password also incorrect - cannot open PDF")

417

pdf = None

418

419

if pdf:

420

# Analyze encryption settings

421

enc_info = pdf.encryption_info

422

print(f"Encryption method: {enc_info.method}")

423

print(f"Encryption strength: {enc_info.bits} bits")

424

print(f"Owner access: {enc_info.owner_password_matched}")

425

print(f"User access: {enc_info.user_password_matched}")

426

427

# Check permissions

428

perms = enc_info.permissions

429

print(f"Can extract text: {perms.extract}")

430

print(f"Can print high-res: {perms.print_highres}")

431

print(f"Can modify: {perms.modify_other}")

432

433

pdf.close()

434

```

435

436

### Password Protection Levels

437

438

```python

439

import pikepdf

440

441

def create_protection_levels():

442

"""Demonstrate different levels of PDF protection."""

443

444

# Level 1: Basic password protection, full permissions

445

pdf1 = pikepdf.new()

446

pdf1.add_blank_page()

447

448

basic_encryption = pikepdf.Encryption(

449

user='simple123',

450

owner='admin123',

451

R=4, # 128-bit encryption

452

aes=True

453

)

454

# Default permissions allow everything

455

456

pdf1.save('basic_protected.pdf', encryption=basic_encryption)

457

pdf1.close()

458

print("Created: basic_protected.pdf (password required, full permissions)")

459

460

# Level 2: Moderate protection with some restrictions

461

pdf2 = pikepdf.new()

462

pdf2.add_blank_page()

463

464

moderate_permissions = pikepdf.Permissions(

465

extract=False, # No copying

466

print_highres=False, # Low-res printing only

467

modify_other=False # No content modification

468

)

469

470

moderate_encryption = pikepdf.Encryption(

471

user='user456',

472

owner='owner456',

473

R=6, # 256-bit AES

474

allow=moderate_permissions

475

)

476

477

pdf2.save('moderate_protected.pdf', encryption=moderate_encryption)

478

pdf2.close()

479

print("Created: moderate_protected.pdf (restricted copying/printing)")

480

481

# Level 3: High security with minimal permissions

482

pdf3 = pikepdf.new()

483

pdf3.add_blank_page()

484

485

strict_permissions = pikepdf.Permissions(

486

accessibility=True, # Keep accessibility

487

extract=False, # No copying

488

print_lowres=False, # No printing at all

489

print_highres=False,

490

modify_annotation=False, # No annotations

491

modify_form=False, # No form filling

492

modify_other=False, # No modifications

493

assemble=False # No page operations

494

)

495

496

strict_encryption = pikepdf.Encryption(

497

user='readonly789',

498

owner='superadmin789',

499

R=6, # Strongest encryption

500

allow=strict_permissions,

501

metadata=True # Encrypt metadata too

502

)

503

504

pdf3.save('strict_protected.pdf', encryption=strict_encryption)

505

pdf3.close()

506

print("Created: strict_protected.pdf (view-only, no operations allowed)")

507

508

create_protection_levels()

509

```

510

511

### Removing Encryption

512

513

```python

514

import pikepdf

515

516

def remove_encryption(input_file, password, output_file):

517

"""Remove encryption from a PDF if password is known."""

518

519

try:

520

# Open encrypted PDF

521

pdf = pikepdf.open(input_file, password=password)

522

523

# Check if we have owner access (required to remove encryption)

524

if not pdf.encryption_info.owner_password_matched:

525

print("Warning: Only user access - encryption removal may not work")

526

527

# Save without encryption (no encryption parameter)

528

pdf.save(output_file)

529

pdf.close()

530

531

print(f"Successfully removed encryption: {input_file} -> {output_file}")

532

533

# Verify the result

534

verify_pdf = pikepdf.open(output_file)

535

print(f"Verification: New PDF has {len(verify_pdf.pages)} pages")

536

print(f"Verification: PDF is encrypted: {verify_pdf.is_encrypted}")

537

verify_pdf.close()

538

539

except pikepdf.PasswordError:

540

print(f"Error: Incorrect password for {input_file}")

541

except Exception as e:

542

print(f"Error removing encryption: {e}")

543

544

# Remove encryption from various protection levels

545

remove_encryption('basic_protected.pdf', 'admin123', 'basic_unprotected.pdf')

546

remove_encryption('moderate_protected.pdf', 'owner456', 'moderate_unprotected.pdf')

547

remove_encryption('strict_protected.pdf', 'superadmin789', 'strict_unprotected.pdf')

548

```

549

550

### Changing Encryption Settings

551

552

```python

553

import pikepdf

554

555

def upgrade_encryption(input_file, old_password, new_encryption, output_file):

556

"""Upgrade encryption settings for an existing PDF."""

557

558

try:

559

# Open with existing password

560

pdf = pikepdf.open(input_file, password=old_password)

561

562

print(f"Current encryption: {pdf.encryption_info.method}, "

563

f"{pdf.encryption_info.bits} bits")

564

565

# Save with new encryption settings

566

pdf.save(output_file, encryption=new_encryption)

567

pdf.close()

568

569

print(f"Upgraded encryption: {input_file} -> {output_file}")

570

571

# Verify new encryption

572

verify_pdf = pikepdf.open(output_file, password=new_encryption.owner_password)

573

new_info = verify_pdf.encryption_info

574

print(f"New encryption: {new_info.method}, {new_info.bits} bits")

575

verify_pdf.close()

576

577

except Exception as e:

578

print(f"Error upgrading encryption: {e}")

579

580

# Upgrade to strongest encryption

581

new_encryption = pikepdf.Encryption(

582

owner='new_super_secure_password_2024',

583

user='new_user_password_2024',

584

R=6, # 256-bit AES

585

aes=True,

586

metadata=True,

587

allow=pikepdf.Permissions(

588

extract=False,

589

print_highres=True, # Allow high-quality printing

590

modify_other=False

591

)

592

)

593

594

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

595

```

596

597

### Bulk Encryption Operations

598

599

```python

600

import pikepdf

601

import os

602

from pathlib import Path

603

604

def encrypt_directory(directory_path, encryption_settings, password):

605

"""Encrypt all PDFs in a directory."""

606

607

directory = Path(directory_path)

608

encrypted_dir = directory / 'encrypted'

609

encrypted_dir.mkdir(exist_ok=True)

610

611

pdf_files = list(directory.glob('*.pdf'))

612

results = {'success': [], 'failed': []}

613

614

for pdf_file in pdf_files:

615

try:

616

# Skip already encrypted files (basic check)

617

try:

618

test_pdf = pikepdf.open(pdf_file)

619

if test_pdf.is_encrypted:

620

print(f"Skipping already encrypted: {pdf_file.name}")

621

test_pdf.close()

622

continue

623

test_pdf.close()

624

except pikepdf.PasswordError:

625

print(f"Skipping password-protected: {pdf_file.name}")

626

continue

627

628

# Encrypt the PDF

629

pdf = pikepdf.open(pdf_file)

630

output_path = encrypted_dir / pdf_file.name

631

pdf.save(output_path, encryption=encryption_settings)

632

pdf.close()

633

634

results['success'].append(pdf_file.name)

635

print(f"Encrypted: {pdf_file.name}")

636

637

except Exception as e:

638

results['failed'].append((pdf_file.name, str(e)))

639

print(f"Failed to encrypt {pdf_file.name}: {e}")

640

641

print(f"\nEncryption complete:")

642

print(f" Successful: {len(results['success'])} files")

643

print(f" Failed: {len(results['failed'])} files")

644

645

return results

646

647

# Bulk encryption with standard settings

648

bulk_encryption = pikepdf.Encryption(

649

owner='bulk_owner_2024',

650

user='bulk_user_2024',

651

R=6,

652

allow=pikepdf.Permissions(

653

print_highres=True,

654

extract=False,

655

modify_other=False

656

)

657

)

658

659

# Encrypt all PDFs in current directory

660

# results = encrypt_directory('.', bulk_encryption, 'bulk_user_2024')

661

```

662

663

### Security Analysis Tool

664

665

```python

666

import pikepdf

667

from pathlib import Path

668

669

def analyze_pdf_security(pdf_path, password=None):

670

"""Analyze the security settings of a PDF file."""

671

672

try:

673

# Try to open without password first

674

try:

675

pdf = pikepdf.open(pdf_path)

676

encrypted = False

677

except pikepdf.PasswordError:

678

if password:

679

pdf = pikepdf.open(pdf_path, password=password)

680

encrypted = True

681

else:

682

return {"error": "PDF is encrypted but no password provided"}

683

684

analysis = {

685

"file": str(pdf_path),

686

"encrypted": encrypted,

687

"pages": len(pdf.pages),

688

"pdf_version": pdf.pdf_version

689

}

690

691

if encrypted:

692

enc_info = pdf.encryption_info

693

analysis.update({

694

"encryption_method": enc_info.method,

695

"encryption_bits": enc_info.bits,

696

"owner_access": enc_info.owner_password_matched,

697

"user_access": enc_info.user_password_matched,

698

"permissions": {

699

"extract_text": enc_info.permissions.extract,

700

"print_lowres": enc_info.permissions.print_lowres,

701

"print_highres": enc_info.permissions.print_highres,

702

"modify_annotations": enc_info.permissions.modify_annotation,

703

"modify_forms": enc_info.permissions.modify_form,

704

"modify_other": enc_info.permissions.modify_other,

705

"assemble_document": enc_info.permissions.assemble,

706

"accessibility": enc_info.permissions.accessibility

707

}

708

})

709

710

pdf.close()

711

return analysis

712

713

except Exception as e:

714

return {"error": str(e)}

715

716

def security_report(directory_path):

717

"""Generate a security report for all PDFs in a directory."""

718

719

directory = Path(directory_path)

720

pdf_files = list(directory.glob('*.pdf'))

721

722

print(f"PDF Security Report for: {directory}")

723

print("=" * 60)

724

725

encrypted_count = 0

726

unencrypted_count = 0

727

error_count = 0

728

729

for pdf_file in pdf_files:

730

analysis = analyze_pdf_security(pdf_file)

731

732

if "error" in analysis:

733

print(f"\nโŒ {pdf_file.name}: {analysis['error']}")

734

error_count += 1

735

elif analysis["encrypted"]:

736

print(f"\n๐Ÿ”’ {pdf_file.name}: ENCRYPTED")

737

print(f" Method: {analysis['encryption_method']}")

738

print(f" Strength: {analysis['encryption_bits']} bits")

739

print(f" Access Level: {'Owner' if analysis['owner_access'] else 'User'}")

740

741

# Show key restrictions

742

perms = analysis["permissions"]

743

restrictions = []

744

if not perms["extract_text"]: restrictions.append("No copying")

745

if not perms["print_highres"]: restrictions.append("No high-res printing")

746

if not perms["modify_other"]: restrictions.append("No modification")

747

748

if restrictions:

749

print(f" Restrictions: {', '.join(restrictions)}")

750

else:

751

print(" Restrictions: None")

752

753

encrypted_count += 1

754

else:

755

print(f"\n๐Ÿ”“ {pdf_file.name}: UNPROTECTED")

756

unencrypted_count += 1

757

758

print(f"\n" + "=" * 60)

759

print(f"Summary: {len(pdf_files)} PDFs analyzed")

760

print(f" Encrypted: {encrypted_count}")

761

print(f" Unprotected: {unencrypted_count}")

762

print(f" Errors: {error_count}")

763

764

# Generate security report for current directory

765

# security_report('.')

766

```