PythonでPDFのセキュリティ設定

プログラミング

概要

PythonでPDFの帳票を作る
PythonでReportLabを使って帳票を生成します。帳票の生成自体は難しくありません。ttcフォントが使えないのがちょっと困ったところです。なので、ttcからttfを抽出するコードも解説します。

“PythonでPDFの帳票を作る”という記事で、帳票を作るというシナリオ上PyPDF3で全ての権限を不可にする設定をしました。

PDFファイルの権限を制御する際、Python用のライブラリであるPyPDF3は簡単に扱えますが、現在非推奨であり、細かな権限設定ができません。

そのため、Java製のツールであるPDFBoxを使用することで、きめ細かい権限制御が可能になります。

本記事では、PythonとPDFBoxを連携させる方法を説明します。

インストール等

pdfbox

Apache PDFBox | A Java PDF Library
The Apache PDFBox™ library is an open source Java tool for working with PDF documents. This project allows creation of n...

ここから pdfbox-app-3.0.3.jar をダウンロードして使います。

Py4j

pip install py4j

としてインストールします。

javaと通信するために使います。

PDFBox

pdfbox-app-x.x.x.jar は、コマンドとしても使えます。

java -jar pdfbox-app-3.0.3.jar Encrypt -O 1111 -canPrint -canExtractContent -i input.pdf -o output.pdf

このように実行すると input.pdf を読み込んで 出力ファイル output.pdf が以下の様に設定されます。

  • オーナーパスワード : “1111”
  • “-canPrint” : 印刷不可
  • “-canExtractContent” : コピー不可

権限設定の確認

AcrobatReader で確認できる権限は、

Acrobat Readerでは一部の権限が「許可されていない」と表示される場合がありますが、これはViewerによる制約です。
詳細な権限は、Adobe Acrobat Proなどの専用ソフトで確認できます。

そこで、次のコードで確認できるようにします。

PDFの権限設定確認コード

以下のコードを環境に合わせて pdfbox-app-x.x.x.jar の場所などを変更して使ってください。

from py4j.java_gateway import JavaGateway, GatewayParameters, launch_gateway

# PDFBoxのJARファイルのパスを指定
PDFBOX_JAR_PATH = r"C:\Users\[ユーザー名]\Documents\Develop" \
    r"\Python\test01\pdfbox-app-3.0.3.jar" # 環境に合わせて変更

def read_pdf_permissions(file_path):
    # Gatewayを起動
    port = launch_gateway(classpath=PDFBOX_JAR_PATH)
    gateway = JavaGateway(gateway_parameters=GatewayParameters(port=port))

    try:
        # Javaクラスのロード
        Loader = gateway.jvm.org.apache.pdfbox.Loader
        Permission = gateway.jvm.org.apache.pdfbox.pdmodel.encryption.AccessPermission

        # PDFを読み込む
        document = Loader.loadPDF(gateway.jvm.java.io.File(file_path))
        permissions = document.getCurrentAccessPermission()  # ドキュメントの権限を取得
        
        # 許可設定を取得
        permissions_dict = {
            "can_print": permissions.canPrint(),
            "can_modify": permissions.canModify(),
            "can _Assemble_document": permissions.canAssembleDocument(),
            "can_extract_content": permissions.canExtractContent(),
            "can_fill_in_form": permissions.canFillInForm(),
            "can_print_faithful": permissions.canPrintFaithful(),
            "can_modify_annotations": permissions.canModifyAnnotations(),
            "can_extract_for_accessibility": permissions.canExtractForAccessibility(),
            "can_assemble_document": permissions.canAssembleDocument(),
            "is_read_only": permissions.isReadOnly(),
        }
        document.close()
        return permissions_dict
    finally:
        gateway.shutdown()

# 使用例
if __name__ == "__main__":
    pdf_path = "output.pdf"  # 読み取りたいPDFファイルのパス
    permissions = read_pdf_permissions(pdf_path)
    print("PDF Permissions:")
    for key, value in permissions.items():
        print(f"{key}: {value}")

 

コード実行結果

このようにPDFの権限設定が表示されます。

権限の種類

以下は、コードの一部です。

    # アクセス許可を設定
    access_permission = AccessPermission()
    access_permission.setCanPrint(False) # 印刷を許可するか (true: 許可, false: 禁止)
    # access_permission.setCanModify(False) # コンテンツの変更を許可するか
    access_permission.setCanExtractContent(False) # テキストや画像の抽出を許可するか
    # access_permission.setCanExtractForAccessibility(False) # アクセシビリティのためのテキスト抽出を許可するか
    # access_permission.setCanFillInForm(False) # フォームの記入を許可するか
    # access_permission.setCanModifyAnnotations(False) # 注釈やフォームフィールドの変更を許可するか
    access_permission.setCanAssembleDocument(False) # ドキュメントの結合を許可するか
    # access_permission.setCanPrintFaithful(False) # 印刷時の品質を維持するか 
    # access_permission.setReadOnly() # 文書アセンブリ不可、ページの抽出不可

これだけのものが設定で変更できます。
setreadOnly() は、他の設定を無視してすべて不可にする設定です。
権限は印刷可能と設定されていても、無視され印刷されません。

詳しくは、以下にドキュメントがありますので参考にしてください。

pdfbox 3.0.3 javadoc (org.apache.pdfbox)

org.apache.pdfbox.pdmodel.encryption.AccessPermission のメソッドの説明をご覧ください。

既存のPDFに権限設定をするコード

コード

from py4j.java_gateway import JavaGateway, GatewayParameters, launch_gateway

# JVMを起動
port = launch_gateway(classpath=r"C:\Users\[ユーザー名]\Documents" \
    r"\Develop\Python\test01\pdfbox-app-3.0.3.jar")
gateway = JavaGateway(gateway_parameters=GatewayParameters(port=port))

# JavaのPDFBox APIを取得
Loader = gateway.jvm.org.apache.pdfbox.Loader
PDDocument = gateway.jvm.org.apache.pdfbox.pdmodel.PDDocument
AccessPermission = gateway.jvm.org.apache.pdfbox.pdmodel.encryption.AccessPermission
StandardProtectionPolicy = gateway.jvm.org.apache.pdfbox. \
    pdmodel.encryption.StandardProtectionPolicy

try:
    # 既存のPDFファイルを読み込み
    input_pdf_path = r"input.pdf"  # 入力PDFのパス
    output_pdf_path = r"output.pdf"  # 出力PDFのパス
    owner_password = "1111"  # オーナーパスワード
    user_password = ""  # ユーザーパスワード

    # PDFをロード
    document = Loader.loadPDF(gateway.jvm.java.io.File(input_pdf_path))

    # アクセス許可を設定
    access_permission = AccessPermission()
    access_permission.setCanPrint(True) # 印刷を許可するか (true: 許可, false: 禁止)
    access_permission.setCanModify(False) # コンテンツの変更
    access_permission.setCanExtractContent(False) # テキストや画像の抽出
    access_permission.setCanExtractForAccessibility(False) # アクセシビリティのためのテキスト抽出
    access_permission.setCanFillInForm(False) # フォームの記入
    access_permission.setCanModifyAnnotations(False) # 注釈やフォームフィールドの変更
    access_permission.setCanAssembleDocument(False) # ドキュメントの結合
    access_permission.setCanPrintFaithful(False) # 印刷時の品質を維持するか
    # access_permission.setReadOnly() # 他の設定は無視 読み取り専用にするか
    
    # 保護ポリシーを作成
    spp = StandardProtectionPolicy(owner_password, user_password, access_permission)
    spp.setEncryptionKeyLength(128)
    document.protect(spp)

    # 保護されたPDFを保存
    document.save(output_pdf_path)
    document.close()

    print(f"権限設定したのPDFを作成しました: {output_pdf_path}")

except Exception as e:
    print(f"エラーが発生しました: {e}")

 

コード解説

Py4jを使って pdfbox-app-3.0.3.jar と通信を行ってコードを実行しています。

from py4j.java_gateway import JavaGateway, GatewayParameters, launch_gateway

# JVMを起動
port = launch_gateway(classpath=r"C:\Users\[ユーザー名]\Documents" \
    r"\Develop\Python\test01\pdfbox-app-3.0.3.jar")
gateway = JavaGateway(gateway_parameters=GatewayParameters(port=port))

gateway を使ってJavaのクラスを呼び出します。

# JavaのPDFBox APIを取得
Loader = gateway.jvm.org.apache.pdfbox.Loader
PDDocument = gateway.jvm.org.apache.pdfbox.pdmodel.PDDocument
AccessPermission = gateway.jvm.org.apache.pdfbox.pdmodel.encryption.AccessPermission
StandardProtectionPolicy = gateway.jvm.org.apache.pdfbox. \
    pdmodel.encryption.StandardProtectionPolicy

PythonのコードでつかうJavaのクラスを取得します。

    owner_password = "1111"  # オーナーパスワード
    user_password = ""  # ユーザーパスワード

ユーザーパスワードを掛けるとファイルを開くときにパスワードを要求されます。
コードとしては、省略できないので空のパスワードを入れるとパスワードを
要求されません。

オーナーパスワードは、権限設定をする場合は必須です。
意味合い的に、誰でも権限を変更可能ではセキュリティの意味がないからです。

    # 保護ポリシーを作成
    spp = StandardProtectionPolicy(owner_password, user_password, access_permission)
    spp.setEncryptionKeyLength(128)
    document.protect(spp)

各種、権限の設定をしたらここで実際にセキュリティを掛けます。

StandardProtectionPolicy()で パスワードと権限設定を保存します。
spp.setEncryptionKeyLength() で 128bitの鍵長を使用してAES暗号化を行い、PDFの権限設定を保護します。

document.protect() で実際に暗号化します。

そして、ファイルを出力して完了です。

まとめ

実務では欠かせないPDFのセキュリティについて説明しました。

PDFの権限設定をPDFBoxを使って細かに設定する方法を説明しました。
権限設定の確認方法と権限の種類についても説明しました。
そして既存のPDFを取り込んで権限設定を設定する方法を説明しました。

これでPythonで帳票などを作る際も安心して使えるのではないでしょうか。

次回は、既存のPDFファイルを書き換えるコードを説明予定です。

お楽しみに。

Commnts

タイトルとURLをコピーしました