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
```