はじめに
Acrobat Services APIは、Adobeが提供するクラウドベースのPDFサービスです。このサービスを利用すると、様々なPDF関連作業を自動処理させる事が出来ます。今回は、気にされる方も多いPDFの保護について、権限制御の種類と実装方法について紹介していきたいと思います。
- PDF保護のニーズ: 情報漏洩の抑制、改ざん防止、機密情報の再利用制限等々
- 本記事の目的: Acrobat Services APIの基本及びPDF保護に関する機能紹介・実装方法
Acrobat Services APIとは?
Acrobat Services APIでは、ユーザ側にてAPIコールを行い、Adobeクラウド側で処理を実行します。ソースコードの内容に従って処理が自動的に実行されますので、システムに組み込む等でユーザの手作業が減らす事が出来ます。
-
特長
- 処理環境:Adobeのクラウド環境にて処理を実行
- 事前準備:必要なものは実行用スクリプトと対象ドキュメントのみ
- 処理品質:高速かつ信頼性の高い変換処理
- 対応形式:PDF、Word、Excel、PowerPoint、画像やテキスト等、様々な形式に対応
- 料金体系:1ヶ月に500回まで無償利用可能、それ以上は有償 (別途契約が必要)
PDFで可能な権限制御
PDFの保護には、閲覧制限で編集制限という大きく分けて二つの種類があります。スクリプト上の宣言では、閲覧制限はユーザーパスワード、編集制限はオーナーパスワードという呼び方をしています。
- ユーザーパスワード (閲覧制限):閲覧を制限
- オーナーパスワード (編集制限):編集を制限 (印刷/コピー/コメント入力等も含む)
ユーザーパスワードは単純に閲覧が出来なくなるだけですが、オーナーパスワードには様々な種類があり、制限したい操作を選択してPDFに保護をかける事が可能です。
低品質印刷 (PRINT_LOW_QUALITY)
低解像度での印刷を制限します。画面閲覧用の資料や、内容確認を目的とした印刷用途の機能です。
高品質印刷 (PRINT_HIGH_QUALITY)
高解像度での印刷を制限します。製版用途や正式な提出資料向けで、この機能を制限した場合でも低品質印刷は可能となります。
文書アセンブリ (EDIT_DOCUMENT_ASSEMBLY)
ページの並べ替え、挿入、削除など、文書構造の編集を制限します。
文書の変更 (EDIT_CONTENT)
既存のテキストや画像の編集など、PDFの内容自体の変更を制限します。
内容のコピー (COPY_CONTENT)
テキストや画像のコピーを制限します。二次利用や引用を想定した資料向けの機能です。
注釈 (EDIT_ANNOTATIONS)
コメントやハイライトなどの注釈操作を制限します。レビュー用途で使われる機能です。
フォームフィールドの入力・署名 (EDIT_FILL_AND_SIGN_FORM_FIELDS)
フォームへの入力や電子署名を制限します。申請書や同意書などで利用される機能です。
補足:権限制御では「制御できない」項目について (アクセシビリティ)
アクセシビリティとは、視覚や操作に制約のある利用者でも文書の内容を理解出来るようにするための仕組みです。
PDFには、スクリーンリーダーなどの支援技術で利用される「アクセシビリティのための内容抽出」が存在します。 この操作は、テキストのコピーとは異なり、文書を読む為の最低限の機能として位置付けられています。
Acrobat Services APIでは、このアクセシビリティ用途の情報抽出を制御する機能は提供されていません。背景としては、アクセシビリティ機能は制限すべきではない操作として扱われている為です。
コピー操作を禁止した場合でも、アクセシビリティ用途の情報抽出は許可された状態となりますが、この挙動は仕様動作となります。
実装編 : PDF保護のソースコード作成
まずは、実行環境の構築を行いましょう。事前の下準備の方法については、以下の別記事にて詳しく説明しておりますので、先にそちらをご参照下さい。なお、今回はPythonベースにて説明していきます。
-
環境構築 (事前準備)
- Acrobat DeveloperのWebサイトでのクレデンシャル作成
- SDKのインストール (pip install pdfservices-sdk)
- 認証情報を環境変数として宣言
<参考記事> Adobe Document Services APIを使ってみよう
- PDF Services APIのトライアル開始 ~ クレデンシャルの認証情報を環境変数として宣言
それでは、スクリプトの紹介をしていきたいと思います。入力元と出力先の情報は以下の通りになるので、お試し頂く方はPDF用のフォルダを作成して頂くか、スクリプト内の入出力情報の指定箇所を修正して下さい。
<入力ファイル情報>
- 入力元:実行スクリプトと同一パスにある
入力ファイル格納先フォルダのPDFを参照 - 入力ファイル:パスワード保護前のPDF
<出力ファイル情報>
- 出力先:実行スクリプトと同一パスにある
出力ファイル格納先フォルダへ出力 - 出力ファイル:パスワード保護がかかったPDF (ファイル名は踏襲)
<認証用ファイル情報>
セキュリティの観点から、クレデンシャル情報はスクリプトに埋め込まずに別ファイルを参照する形にしている為、以下の形でAPI認証用ファイルを設定して下さい。
- 参照ファイル名:credentials.json
- 参照パス:実行スクリプトと同一パス
{
"client_id": "クライントID",
"client_secret": "クライアントシークレット"
}
閲覧を制限 (制限解除パスワード:password)
import logging
import os
import glob
import json
from adobe.pdfservices.operation.auth.service_principal_credentials import ServicePrincipalCredentials
from adobe.pdfservices.operation.exception.exceptions import ServiceApiException, ServiceUsageException, SdkException
from adobe.pdfservices.operation.io.cloud_asset import CloudAsset
from adobe.pdfservices.operation.io.stream_asset import StreamAsset
from adobe.pdfservices.operation.pdf_services import PDFServices
from adobe.pdfservices.operation.pdf_services_media_type import PDFServicesMediaType
from adobe.pdfservices.operation.pdfjobs.jobs.protect_pdf_job import ProtectPDFJob
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.content_encryption import ContentEncryption
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.encryption_algorithm import EncryptionAlgorithm
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.password_protect_params import PasswordProtectParams
from adobe.pdfservices.operation.pdfjobs.result.protect_pdf_result import ProtectPDFResult
# Initialize the logger
logging.basicConfig(level=logging.INFO)
class ProtectPDF:
def __init__(self):
try:
input_folder = '入力ファイル格納先'
output_folder = '出力ファイル格納先'
os.makedirs(output_folder, exist_ok=True)
pdf_files = glob.glob(os.path.join(input_folder, '*.pdf'))
if not pdf_files:
logging.error("PDFファイルが見つかりません。")
return
# Credentialsの読み込み
creds = json.load(open('credentials.json'))
credentials = ServicePrincipalCredentials(
client_id=creds["client_id"],
client_secret=creds["client_secret"]
)
# PDF Servicesインスタンス生成
pdf_services = PDFServices(credentials=credentials)
pdf_path = pdf_files[0]
logging.info(f"対象PDF: {pdf_path}")
with open(pdf_path, "rb") as file:
input_stream = file.read()
# PDFアップロード
input_asset = pdf_services.upload(input_stream=input_stream, mime_type=PDFServicesMediaType.PDF)
# PDFパスワード付与ジョブ作成 ※パスワードはpasswordと設定
protect_pdf_params = PasswordProtectParams(
user_password='password',
encryption_algorithm=EncryptionAlgorithm.AES_256,
content_encryption=ContentEncryption.ALL_CONTENT,
)
# PDFパスワード付与ジョブインスタンス生成
protect_pdf_job = ProtectPDFJob(input_asset=input_asset, protect_pdf_params=protect_pdf_params)
# ジョブ実行 & 結果取得
location = pdf_services.submit(protect_pdf_job)
pdf_services_response = pdf_services.get_job_result(location, ProtectPDFResult)
# パスワード付与後のPDFダウンロード
result_asset: CloudAsset = pdf_services_response.get_result().get_asset()
stream_asset: StreamAsset = pdf_services.get_content(result_asset)
# 出力ファイル名を決定
base_filename = os.path.splitext(os.path.basename(pdf_path))[0]
output_file_path = os.path.join(output_folder, f"{base_filename}.pdf")
with open(output_file_path, "wb") as file:
file.write(stream_asset.get_input_stream())
logging.info(f"パスワード付与完了: {output_file_path}")
except (ServiceApiException, ServiceUsageException, SdkException, Exception) as e:
logging.exception(f'Exception encountered while executing operation: {e}')
if __name__ == "__main__":
ProtectPDF()
実行結果
低品質印刷と注釈以外を制限 (制限解除パスワード:password)
import logging
import os
import glob
import json
from adobe.pdfservices.operation.auth.service_principal_credentials import ServicePrincipalCredentials
from adobe.pdfservices.operation.exception.exceptions import ServiceApiException, ServiceUsageException, SdkException
from adobe.pdfservices.operation.io.cloud_asset import CloudAsset
from adobe.pdfservices.operation.io.stream_asset import StreamAsset
from adobe.pdfservices.operation.pdf_services import PDFServices
from adobe.pdfservices.operation.pdf_services_media_type import PDFServicesMediaType
from adobe.pdfservices.operation.pdfjobs.jobs.protect_pdf_job import ProtectPDFJob
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.content_encryption import ContentEncryption
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.encryption_algorithm import EncryptionAlgorithm
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.password_protect_params import PasswordProtectParams
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.permission import Permission
from adobe.pdfservices.operation.pdfjobs.params.protect_pdf.permissions import Permissions
from adobe.pdfservices.operation.pdfjobs.result.protect_pdf_result import ProtectPDFResult
# Initialize the logger
logging.basicConfig(level=logging.INFO)
class ProtectPDF:
def __init__(self):
try:
input_folder = '入力ファイル格納先'
output_folder = '出力ファイル格納先'
os.makedirs(output_folder, exist_ok=True)
pdf_files = glob.glob(os.path.join(input_folder, '*.pdf'))
if not pdf_files:
logging.error("PDFファイルが見つかりません。")
return
# Credentialsの読み込み
creds = json.load(open('credentials.json'))
credentials = ServicePrincipalCredentials(
client_id=creds["client_id"],
client_secret=creds["client_secret"]
)
# PDF Servicesインスタンス生成
pdf_services = PDFServices(credentials=credentials)
pdf_path = pdf_files[0]
logging.info(f"対象PDF: {pdf_path}")
with open(pdf_path, "rb") as file:
input_stream = file.read()
# PDFアップロード
input_asset = pdf_services.upload(input_stream=input_stream, mime_type=PDFServicesMediaType.PDF)
# Create new permissions instance and add the required permissions
permissions = Permissions()
permissions.add_permission(Permission.PRINT_LOW_QUALITY)
permissions.add_permission(Permission.EDIT_ANNOTATIONS)
# PDFパスワード付与ジョブ作成
protect_pdf_params = PasswordProtectParams(
owner_password='password',
encryption_algorithm=EncryptionAlgorithm.AES_256,
permissions=permissions,
content_encryption=ContentEncryption.ALL_CONTENT_EXCEPT_METADATA,
)
# PDFパスワード付与ジョブインスタンス生成
protect_pdf_job = ProtectPDFJob(input_asset=input_asset, protect_pdf_params=protect_pdf_params)
# ジョブ実行 & 結果取得
location = pdf_services.submit(protect_pdf_job)
pdf_services_response = pdf_services.get_job_result(location, ProtectPDFResult)
# パスワード付与後のPDFダウンロード
result_asset: CloudAsset = pdf_services_response.get_result().get_asset()
stream_asset: StreamAsset = pdf_services.get_content(result_asset)
# 出力ファイル名を決定
base_filename = os.path.splitext(os.path.basename(pdf_path))[0]
output_file_path = os.path.join(output_folder, f"{base_filename}.pdf")
with open(output_file_path, "wb") as file:
file.write(stream_asset.get_input_stream())
logging.info(f"パスワード付与完了: {output_file_path}")
except (ServiceApiException, ServiceUsageException, SdkException, Exception) as e:
logging.exception(f'Exception encountered while executing operation: {e}')
if __name__ == "__main__":
ProtectPDF()
スクリプトの内容を確認すると分かると思いますが、オーナーパスワード(編集制限)の場合は、ベースとしては全ての機能が制限されており、利用を許可する機能のパーミッションを追加で宣言していくという流れになります。
実行結果
権限制御の包含関係及び宣言順序について
Acrobat Services APIでは、PDFの操作制限をパーミッションとして指定することが出来ますが、全てのパーミッションが完全に独立している訳では無く、一部には「包含関係」や「宣言順序による影響」が存在します。設定する際に混乱してしまわない様、ここでは注意すべきポイントを整理します。
包含関係について
-
印刷 (PRINT_LOW_QUALITY / PRINT_HIGH_QUALITY)
高解像度印刷及び低解像度印刷を許可します。
高解像度印刷を許可した場合は、低解像度印刷も許可されますが、低解像度印刷のみを許可した場合は、高解像度印刷は許可されません。
-
文書アセンブリ (EDIT_DOCUMENT_ASSEMBLY)
ページの並べ替えや削除などを許可します。
単独での許可が可能です。
-
文書の変更 (EDIT_CONTENT)
PDFの内容編集を許可します。
文書アセンブリおよびフォームフィールドの入力・署名も許可されます。
また、内容のコピーが許可されていない場合でも、編集モードにした場合は内容のコピーが可能です。
-
内容のコピー (COPY_CONTENT)
閲覧モードでのテキストや画像のコピーを許可します。
他のパーミッションからは独立しており、包含関係はありません。
-
注釈 (EDIT_ANNOTATIONS)
コメントやハイライトなどの注釈操作を許可します。
フォームフィールドの入力・署名も許可されます。
-
フォームフィールドの入力・署名 (EDIT_FILL_AND_SIGN_FORM_FIELDS)
フォーム入力および署名を許可します。
単独での許可が可能です。
宣言順序について
Acrobat Services APIによるPDF保護では、 複数のパーミッションを指定する場合、宣言順序によって最終的な制限状態が変わる事があります。これは、API側で権限制御が段階的に適用される仕組みによる影響です。
想定通りの結果を得る為には、編集に関わるパーミッションを先に指定し、利用に関わるパーミッションを後に指定する必要がありますので、その点ご注意下さい。
permissions = Permissions()
# 編集系 (先に宣言する)
permissions.add_permission(Permission.EDIT_CONTENT)
permissions.add_permission(Permission.EDIT_DOCUMENT_ASSEMBLY)
permissions.add_permission(Permission.EDIT_ANNOTATIONS)
permissions.add_permission(Permission.EDIT_FILL_AND_SIGN_FORM_FIELDS)
# 利用系 (後に宣言する)
permissions.add_permission(Permission.COPY_CONTENT)
permissions.add_permission(Permission.PRINT_HIGH_QUALITY)
permissions.add_permission(Permission.PRINT_LOW_QUALITY)
補足:PRINT (印刷) とCOPY (内容のコピー) の同時許可について
検証の過程で、API (Python SDK) から PRINT_* と COPY_CONTENT を同時に許可した場合に、宣言順序によってどちらか一方の許可が欠落する挙動を確認しました。こちらはAPI側の不具合として修正を進めております。(修正完了後、この内容は削除予定)
ケース別プリセット紹介 (編集制限)
ここでは、よくありそうな編集制限の組み合わせを記載しております。
レビュー依頼:コメント入力・フォーム入力・署名・低解像度印刷のみ許可
permissions = Permissions()
permissions.add_permission(Permission.EDIT_ANNOTATIONS)
permissions.add_permission(Permission.PRINT_LOW_QUALITY)
社外閲覧のみ:何も許可しない (印刷・コピー・編集等すべて不可)
permissions = Permissions()
製版印刷向け:高解像度印刷のみ許可 (低解像度印刷は可能)
permissions = Permissions()
permissions.add_permission(Permission.PRINT_HIGH_QUALITY)
Power Automateのアクション紹介
Microsoft Power Automateと連携
Microsoft Power Automate を利用している場合は、Adobeが提供しているPower Automate向けのコネクタであるAdobe PDF サービスを利用する事が可能です。
※注意:プレミアムコネクタが必要
利用開始方法
左メニューにある接続 ⇒ 新しい接続からAdobeで検索すると、Adobe PDF サービスが表示されるので、選択後にお持ちのクレデンシャル情報を入力したら事前準備としては完了です。
- 認証タイプ:Oauth サーバー間の資格情報
- クライアント ID:Adobe Services APIのクライアントID
- クライアント シークレット:Adobe Services APIのクライアントシークレット
今回の記事のトピックであるPDF保護のアクションを利用する場合、アクション追加の検索窓でAdobeと入力するとAdobe PDF サービスという項目が表示されるので、閲覧制限の場合はPDF を表示から保護する、編集制限の場合はPDF をコピー、編集、印刷から保護するを選択してアクションを追加します。
パラメーター設定画面 (PDF を表示から保護する)
パラメーター設定画面 (PDF をコピー、編集、印刷から保護する)
よくあるエラーとトラブルシューティング
-
実行時にエラーが発生する / ジョブが失敗する
⇒credentials.jsonの内容 (client_id / client_secret) が正しく設定されているか、入出力ファイル用のフォルダが作成されているか、入力ファイル用のフォルダにPDFファイルが存在しているかを確認して下さい。また、SDKがちゃんとインストールされているかも併せて確認して下さい。
-
権限を指定したのに、想定と異なる制限状態になる
⇒ 複数のパーミッションを組み合わせる場合、宣言順序や包含関係の影響により想定と異なる制限になる場合があります。権限制御の宣言順序及び包含関係についての項目を参照し、指定方法が正しいかを確認して下さい。
-
印刷の許可で意図と違う結果になる
⇒PRINT_HIGH_QUALITYはPRINT_LOW_QUALITYを内包します。低解像度印刷のみを許可したい場合は、高解像度印刷を指定しない様にして下さい。また、低解像度印刷のみを許可した場合、表示上は印刷が許可になっていても、高解像度印刷は出来ない形になっています。
-
それでも解決しない場合
⇒ 公式ドキュメントや、開発者向けフォーラムには、エラーコードや具体的な事例が多く掲載されています。実装前後での挙動確認や仕様の裏取りに活用すると、原因特定がしやすくなります。
最後に
今回は、PDF保護のAPIに関して紹介しました。個人で利用する事もシステムに組み込んで利用する事も出来ますので、皆さんの豊富なアイディアで上手に活用して頂けたらと思います。また、Acrobat Services API Use Casesのページや、Adobe Document Cloud 公式YouTubeにも活用のヒントがありますので、よろしければこちらもご参照下さい。
もし本格的に導入を検討したい方がおられましたら、以下のページからご相談頂く事が可能なので、是非ともお気軽にお問合せ下さい。







