配布するファイルが改ざんされていないことを受け取る側に証明したいときは、秘密鍵でファイルに電子署名を付ける。受け取る側は、公開鍵で署名を検証する。openssl dgst -signと-verifyを使う。
署名する側は、秘密鍵でファイルのハッシュ値に署名して、署名ファイルを作る。
$ openssl dgst -sha256 -sign rsa.key -out msg.sig msg.txt
検証する側は、公開鍵と署名ファイルを使って、ファイルが改ざんされていないか確認する。
$ openssl dgst -sha256 -verify rsa.pub -signature msg.sig msg.txt
Verified OK
署名ファイル(msg.sig)と公開鍵(rsa.pub)を、署名対象のファイル(msg.txt)とセットで渡す。秘密鍵は署名する側だけが持つ。
実行例はUbuntu 24.04のOpenSSL 3.0.13のもの。
署名に使う鍵を用意する
秘密鍵と公開鍵は、genpkeyとpkeyで作る。
$ openssl genpkey -algorithm RSA -out rsa.key
$ openssl pkey -in rsa.key -pubout -out rsa.pub
参考: 【openssl】秘密鍵を作成する (genpkey)
参考: 【openssl】秘密鍵から公開鍵を取り出す (pkey)
署名の流れ
署名は、ファイルのハッシュ値を計算して、そのハッシュ値に秘密鍵で署名する処理である。検証では、公開鍵で署名を検証して、署名時のハッシュ値と現在のファイルのハッシュ値が一致するか確認する。
sequenceDiagram participant S as 署名する側 participant R as 検証する側 S->>S: ファイルのハッシュ値を計算 S->>S: 秘密鍵でハッシュ値に署名 S->>R: ファイルと署名ファイルを渡す R->>R: ファイルのハッシュ値を計算 R->>R: 公開鍵で署名を検証
ハッシュアルゴリズムは、署名と検証で同じ値を指定する。署名に-sha256を使った場合、検証でも-sha256を指定する。
$ openssl dgst -sha1 -sign rsa.key -out sha1.sig msg.txt
$ openssl dgst -sha256 -verify rsa.pub -signature sha1.sig msg.txt
Verification failure
参考: 【openssl】ファイルのハッシュ値を計算する (dgst)
改ざんや鍵の違いを検出する
ファイルの内容が変わると、検証に失敗する。終了コードは1になる。
$ echo "hello tampered world" > msg2.txt
$ openssl dgst -sha256 -verify rsa.pub -signature msg.sig msg2.txt
Verification failure
署名した鍵とは別の公開鍵で検証しても、失敗する。
$ openssl dgst -sha256 -verify other.pub -signature msg.sig msg.txt
Verification failure
成功した場合の終了コードは0である。スクリプトでは、終了コードで成否を判定できる。
$ openssl dgst -sha256 -verify rsa.pub -signature msg.sig msg.txt > /dev/null && echo OK
OK
署名ファイルが存在しない場合は、別のエラーになる。
$ openssl dgst -sha256 -verify rsa.pub -signature nosuch.sig msg.txt
Error opening signature file nosuch.sig
ECDSAで署名する
ECDSAの鍵でも、同じコマンドで署名できる。署名のサイズは、RSA 2048ビットで256バイト、ECDSA P-256で70〜72バイト程度である。
$ openssl dgst -sha256 -sign ec.key -out msg.ec.sig msg.txt
$ wc -c msg.ec.sig
70 msg.ec.sig
$ openssl dgst -sha256 -verify ec.pub -signature msg.ec.sig msg.txt
Verified OK
ECDSAの署名は、署名のたびに値が変わるため、サイズも毎回変わる。検証は成功する。
Ed25519で署名する
Ed25519は、dgst -signでは署名できない。ハッシュ値ではなく、ファイル全体に対して署名する方式のためである。
$ openssl dgst -sha256 -sign ed.key -out ed.sig msg.txt
Key type not supported for this operation
openssl pkeyutl -sign -rawinを使う。
$ openssl pkeyutl -sign -inkey ed.key -rawin -in msg.txt -out ed.sig
$ openssl pkeyutl -verify -pubin -inkey ed.pub -rawin -in msg.txt -sigfile ed.sig
Signature Verified Successfully
署名の検証に失敗した場合は、Signature Verification Failureと表示して、終了コードが1になる。
$ openssl pkeyutl -verify -pubin -inkey ed.pub -rawin -in msg2.txt -sigfile ed.sig
Signature Verification Failure
-rawinは、OpenSSL 3.0以降で使える。
pkeyutlでRSAの署名を作る
pkeyutlは、RSAやECDSAの署名にも使える。-rawinと-digestを組み合わせると、dgst -signと同じ署名を作れる。
$ openssl pkeyutl -sign -inkey rsa.key -rawin -digest sha256 -in msg.txt -out q.sig
$ openssl dgst -sha256 -verify rsa.pub -signature q.sig msg.txt
Verified OK
RSAのPKCS#1 v1.5署名は、同じ鍵と同じファイルであれば、毎回同じ署名になる。dgst -signとpkeyutl -sign -rawin -digestの署名は、バイト単位で一致する。
ハッシュ値を先に計算して、ハッシュ値に対して署名する方法もある。
$ openssl dgst -sha256 -binary msg.txt > msg.dgst
$ openssl pkeyutl -sign -inkey rsa.key -in msg.dgst -pkeyopt digest:sha256 -out p.sig
$ openssl dgst -sha256 -verify rsa.pub -signature p.sig msg.txt
Verified OK
RSA-PSSで署名する
RSAの署名には、従来のPKCS#1 v1.5のほかに、より新しいRSA-PSSがある。-sigoptでパディング方式を指定する。
$ openssl dgst -sha256 -sign rsa.key -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -out pss.sig msg.txt
$ openssl dgst -sha256 -verify rsa.pub -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 -signature pss.sig msg.txt
Verified OK
検証でも、同じ-sigoptを指定する。指定しないと検証に失敗する。
$ openssl dgst -sha256 -verify rsa.pub -signature pss.sig msg.txt
Verification failure
署名をテキストで扱う
署名ファイルはバイナリである。メールやチャットで渡す場合は、Base64に変換する。
$ openssl base64 -A -in msg.sig
PmXcLre9ylBnwec6BSiuV8ab9foMl+SBqzG2ioQM...
受け取る側は、Base64をデコードして、署名ファイルに戻す。
$ openssl base64 -d -A -in msg.sig.b64 -out msg.sig
参考: 【openssl】Base64でエンコード・デコードする (base64)
証明書を使って検証する
公開鍵の代わりに、証明書を受け取る場合は、証明書から公開鍵を取り出して検証する。
$ openssl x509 -in signer.crt -noout -pubkey > signer.pub
$ openssl dgst -sha256 -verify signer.pub -signature msg.sig msg.txt
Verified OK
この方法では、署名が証明書の持ち主の秘密鍵で作られたかを確認できるが、証明書自体が信頼できるかは確認しない。証明書の検証は、別途openssl verifyで実施する。
参考: 【openssl】証明書チェーンを検証する (verify)
パスフレーズ付きの秘密鍵で署名する
パスフレーズで保護された秘密鍵は、-passinでパスフレーズを渡す。
$ openssl dgst -sha256 -sign rsa-enc.key -passin env:KEY_PW -out msg.sig msg.txt
-passinを省略すると、対話的に入力を求められる。
署名と暗号化の違い
署名は、ファイルの改ざんを検出する仕組みである。ファイルの内容は隠されない。ファイルを秘匿する場合は、暗号化を別途行う。
参考: 【openssl】ファイルをパスワードで暗号化・復号する (enc)
バージョンによる違い
OpenSSL 3.6でも、ここまでの例は同じように動く。OpenSSL 3.6で作ったRSAの署名は、LibreSSLで検証でき、同じ鍵とファイルからLibreSSLが作る署名とバイト単位で一致する。
macOS標準の/usr/bin/openssl(LibreSSL 3.3.6)では、以下が異なる。
pkeyutlの-rawinに対応していない- Ed25519に対応していない。Ed25519の公開鍵は読み込めない
Ed25519の署名が必要な場合は、Homebrewなどでインストールしたopensslを使う。
