環境情報
- 使用OS:
Windows
- システム環境:
PowerShell
,node.js
,python
OpenSSLインストール
ダウンロード
ダウンロードサイト
https://slproweb.com/products/Win32OpenSSL.html
私は下記のファイルをダウンロードしました。
-
Win64 OpenSSL v3.5.0
-
余談:最近WindowsはOpenSSLが標準搭載だと思っていましたが、そうではなさそうでした
環境変数設定
Windowsシステム環境変数(PATH)にC:\Program Files\OpenSSL-Win64\bin
を追加
動作確認
openssl version
OpenSSL 3.5.0 8 Apr 2025 (Library: OpenSSL 3.5.0 8 Apr 2025)
電子署名の証明書を作成
秘密鍵と証明書(有効期限365日)を作成
-
/CN=Your Name
のYour Name
は署名者名を入力します -
days 365
は有効期間ですので、日数はお好きに変更します
openssl req -x509 -newkey rsa:2048 -keyout private.key -out certificate.crt -days 365 -nodes -subj "/CN=Your Name"
p12ファイルにまとめる
- p12ファイル:秘密鍵と証明書(公開鍵証明書)をセットでまとめて、パスワードで保護したファイル
- こちらで設定したパスワードはPDFに電子署名をするときに必要です
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt
Enter Export Password:
Verifying - Enter Export Password:
PDF作成APP(Node.js)
PDFに署名をする
-
pdfBuffer
はPDFデータです -
passphrase: '****'
の****
にはp12ファイル作成時に設定したパスワードを入力します
import fs from "fs";
import { plainAddPlaceholder } from 'node-signpdf';
import signPdfInstance from 'node-signpdf';
//・・・中略・・・
// 電子署名用証明書ファイルのパス
const p12Path = path.join(process.cwd(), 'certificate.p12');
const p12Buffer = fs.readFileSync(p12Path);
// Buffer型に変換
const pdfBufferNode = Buffer.from(pdfBuffer);
// プレースホルダー追加
const pdfWithPlaceholder = plainAddPlaceholder({ pdfBuffer: pdfBufferNode, reason: '電子署名' });
// 署名付与(パスワードなしでp12を作成した場合は空文字を指定)
const signedPdf = signPdfInstance.sign(pdfWithPlaceholder, p12Buffer, { passphrase: '****' });
//・・・中略・・・
PDFの署名を検証(Python)
- CAで作ってないためCAは確認しない
- CAとは:認証局 (Certificate Authorities)
- PDFファイルの
SHA256 fingerprint
とSerial number
を確認する- 技術的には、元の証明書なしで
SHA256 fingerprint
とSerial number
を偽造するのは不可能に近いらしいです - 重大な確認が必要な際は、万が一のため、PDFが本物なのかを電話などで確認した方がいいです
- 技術的には、元の証明書なしで
証明書の署名情報を確認
- Passwordは
certificate.p12
作成時のパスワード
openssl pkcs12 -in certificate.p12 -nokeys -clcerts -out mycert.pem
Enter Import Password:
openssl x509 -in mycert.pem -noout -subject -issuer -serial -fingerprint -sha256
subject=CN=Park Test
issuer=CN=Park Test
serial=6D1B3ACBC48A1DCD20494D565AD63687C9E2D93D
sha256 Fingerprint=07:9B:94:AB:0B:0B:96:C6:80:C2:E2:EE:93:00:9C:8E:DF:6B:70:E4:EE:FC:8B:84:7E:0F:67:C2:53:CF:2E:DD
PDFの署名を確認
from pyhanko.pdf_utils.reader import PdfFileReader
from pyhanko.sign.validation import validate_pdf_signature
import contextlib
import io
with open("test2.pdf", "rb") as f:
r = PdfFileReader(f)
sig = r.embedded_signatures[0]
# バリデーション時のstderr出力を抑制
with contextlib.redirect_stderr(io.StringIO()):
status = validate_pdf_signature(sig, key_usage_settings=None)
cert = sig.signer_cert
sha256 = cert.sha256_fingerprint # Already a hex string
sha1 = cert.sha1_fingerprint # Already a hex string
print("SHA256 fingerprint:", sha256)
print("SHA1 fingerprint:", sha1)
print("Subject:", cert.subject.human_friendly)
# Adobeと同じ形式(0xなし大文字16進数)でシリアル番号を表示
try:
serial_bytes = cert.serial_number_bytes
serial_hex = serial_bytes.hex().upper()
print("Serial number:", serial_hex)
except AttributeError:
print("Serial number:", format(cert.serial_number, 'X'))
root@ccd3416ad175:/workspaces/python-tools# python sign-test.py
SHA256 fingerprint: 07 9B 94 AB 0B 0B 96 C6 80 C2 E2 EE 93 00 9C 8E DF 6B 70 E4 EE FC 8B 84 7E 0F 67 C2 53 CF 2E DD
SHA1 fingerprint: 7C 24 8F AA 04 07 16 C4 E0 BA 19 16 BE 08 14 07 A8 5D 45 96
Subject: Common Name: Park Test
Serial number: 6D1B3ACBC48A1DCD20494D565AD63687C9E2D93D
※注意
- 証明書のserialやfingerprintは公開して問題ありません
- 秘密鍵の内容は絶対に公開しないよう注意してください
-
certificate.p12
とprivate.key
は公開してはいけません - 自分で作成した証明書ではなく、認証局(CA)発行の
AATL
対応する証明書を利用すれば、AdobeなどでPDFの署名を検証した際、信頼された署名と表示されます-
AATL
:Adobe Approved Trust List - ※たとえCAが発行した証明書でも、
AATL
に対応していない場合は、Adobeで署名を確認しても「信頼できる」とは表示されません
-
おわりに
ChatGPTとGeminiを活用して調査しました。