こんにちは、その日暮らしです。
とある理由で大量のPDFファイルをパスワード保護する必要に迫られました。
私は、PDF-XChange-EditorというPDFファイルを編集するソフトを持っているのですが、これには複数のPDFファイルを一括で暗号化する機能は備わっていません。このソフトで暗号化を行うとしたらPDFファイルを一つ一つ手作業で暗号化するしかありません。
いろいろとググってみたところ、PyPDF2というPythonのパッケージを使って暗号化する方法を見つけることができました。しかし、この方法で暗号化したPDFファイルの暗号化レベルを調べてみると128ビットRC4とあり、さすがに古すぎます。冒頭のPDF-XChange-Editorで暗号化したときには「互換性」を「Acrobat X」にすることで暗号化レベルは256ビットAESとなり、十分です。そこで、PyPDF2パッケージを使って暗号化したときの暗号化レベルをAESにする方法をググってみたのですが、残念ながら具体的な方法を見つけることはできませんでした。
そのような状況のなかで、PyPDF2と同様にPDFファイルを操作することが可能なpikepdfというパッケージがあることが分かりました。早速試したところ、めでたく暗号化レベルを256ビットAESとすることができました。
当記事では、このときに作成したPythonのコードを紹介していきたいと思います。
準備
Pythonの実行環境の構築がまだの方は、以下の記事を参考にしてPythonの実行環境の構築を済ませてください。ただし、Pythonのインストールまでで結構です。これから紹介するコードを実行するだけならVisual Studio Codeのインストール以降の手順は必要ありません。
コード
以下のコードをコピペし適当なファイル名(拡張子は「.py」)で保存するか、ここからダウンロードしてください。
コードの説明はコード中のコメントを見ればわかると思うので省略しますが、実際に暗号化を行っているのは50行目と51行目になります。残りの部分は、元のDPFファイルのバックアップを行うコードです。
"""
☆☆☆ 指定されたフォルダ内の全PDFファイルを指定されたパスワードで暗号化するツール ☆☆☆
1. pipコマンドをアップデートする。
C:\> python -m pip install --upgrade pip
2. パッケージpikepdfをインストールする。
C:\> pip install pikepdf
3. 実行する。
C:\> python encryptPDF.py -f Folder -p Password [-nb]
-f Folder: 暗号化するPDFファイルが格納されているフォルダ
-p Password: 暗号化に使用するパスワード
-nb: 暗号化時に元のPDFファイルをバックアップしないことを指定するフラグ
"""
import sys
import os
import glob
import shutil
import itertools
import argparse
import pikepdf
def encrypt_pdf(folder: str, password: str, noBackup: bool) -> None:
"""指定されたフォルダ内の全PDFファイルを指定されたパスワードで暗号化する。
Args:
folder (str): 暗号化するPDFファイルが格納されているフォルダ
password (str): 暗号化に使用するパスワード
noBackup (bool): 暗号化時に元のPDFファイルをバックアップしないことを指定するフラグ
"""
# PDFファイルの一覧を取得する。
pdfs = [p.replace(os.sep, '/') for p in glob.glob(folder + '/*.pdf')]
# それぞれのPDFファイルについて・・・。
for pdf in pdfs:
print('**** ' + pdf)
print('---> 暗号化を開始します。')
# PDFファイルを開く。
with pikepdf.open(pdf) as org:
# PDFファイルが既に暗号化されていたら処理をスキップする。
if org.is_encrypted:
print('---> 既に暗号化されています。')
continue
# PDFファイルをバックアップ(リネーム)する。
bak = search_alternate_filepath(pdf.removesuffix('.pdf') + '.bak')
shutil.move(pdf, bak)
# バックアップ(リネーム)したPDFファイルを暗号化し元のPDFファイルのパスで保存する。
with pikepdf.open(bak) as org:
org.save(pdf, encryption=pikepdf.Encryption(user=password))
# noBackupが指定されている場合バックアップ(リネーム)したPDFファイルを削除する。
if noBackup:
os.remove(bak)
print('---> 暗号化が終了しました。')
def search_alternate_filepath(filepath: str) -> str:
"""バックアップ先などに使う代替のファイルパスを検索する。
Args:
filepath (str): オリジナルのファイルパス
Returns:
str: 発見した代替のファイルパス
"""
# ファイルパスをベース名(フルパス)と拡張子に分割する。
base, ext = os.path.splitext(filepath)
# iが1から始まる無限ループを回す。
for i in itertools.count(1):
# 変数filepathが示すファイルが存在していたら・・・。
if os.path.exists(filepath):
# 次の候補のファイルパスを作成する。
filepath = base + '({})'.format(i) + ext
else:
# ファイルが存在しないならここで終了。
return filepath
if __name__ == '__main__':
"""コマンドライン引数を解析してメインルーチンを呼び出す。
"""
# コマンドライン引数を解析する。
parser = argparse.ArgumentParser()
parser.add_argument('-f', '--folder', type=str, required=True, metavar='Folder')
parser.add_argument('-p', '--password', type=str, required=True, metavar='Password')
parser.add_argument('-nb', '--nobackup', action='store_true')
args = parser.parse_args()
# 指定されたフォルダが存在しなければプログラムを終了する。
if not os.path.isdir(args.folder):
print('The specified folder does not exist.')
parser.print_usage()
sys.exit(255)
# メインルーチンを呼び出す。
encrypt_pdf(args.folder, args.password, args.nobackup)
使い方
とりあえず、お約束としてpipコマンドをバージョンアップしておきます。コマンドプロンプトを開き以下のコマンドを実行します。
python -m pip install --upgrade pip
次に、Pythonのpikepdfパッケージをインストールします。上と同様に以下のコマンドを実行します。
pip install pikepdf
いよいよPDFファイルの暗号化を行います。以下のようにコマンドを実行します。以下では、上で紹介したコードを「encryptPDF.py」という名前でフォルダ「C:\Hoge」に保存し、また、フォルダ「C:\Fuga」に置かれたすべてのPDFファイルをパスワード「Piyo」で暗号化する場合のコマンドを示しています。フォルダのパスや暗号化のパスワードはお使いの環境に合わせて適宜変更してください。
python C:\Hoge\encryptPDF.py -f C:\Fuga -p Piyo
このコマンドを実行すると元のファイルが拡張子を「.bak」に変更されてバックアップされ、暗号化されたPDFファイルが元のファイル名で保存されることがわかると思います。
以上、「大量のPDFファイルを暗号化レベル256ビットAESでパスワード保護する方法」でした。
コメント
コメント一覧 (4件)
サンプルコード拝見いたしました。実際試してみました。ありがとうございます。
見積書なのでよく見かけるセキュリティにしたいのですが分かりませんでした。
パスワード入力不要、高解像度の印刷のみ許可、その他は禁止に出來ないでしょうか?
300程のPDFファイルにセキュリティをかけなければなりません。
奥田さん
コメントありがとうございます。
本記事のコード51行目を以下のように修正してみました。
ご希望通りの動作でしょうか?
修正前> org.save(pdf, encryption=pikepdf.Encryption(user=password))
修正後> org.save(pdf, encryption=pikepdf.Encryption(owner=password, allow=pikepdf.Permissions(extract = False, modify_annotation = False, modify_assembly = False, modify_form = False, modify_other = False, print_highres = True, print_lowres = False)))
なお、当サイトのページ「プライバシーポリシー」に記載してますように、当サイトに掲載された内容(上記コードの修正を含む)によって発生したトラブルや損害等の一切の責任を負いかねます。
あらかじめご了承くださいますようお願いいたします。
// その日暮らし
ありがとうございました。おかげさまで一括で300のファイルをやっつけられます。
うまくいったんですね!
お知らせありがとうございました。
// その日暮らし