【証明書入門 第3回】openssl・PowerShellで試す ― 鍵・暗号化・ハッシュ・署名・自己署名証明書

証明書・PKI
スポンサーリンク
スポンサーリンク

「openssl genrsaって打ったら、なんか警告が出るんですけど…」
「Windowsにはopensslがないけど、どうやって試せばいいの?」

ネットで見つけた手順どおりにopensslのコマンドを打ったら、見慣れない警告が出た。そんな経験はありませんか。OpenSSLは3.0で書き方が大きく変わっていて、古い手順書のままだと警告が出たり、動かなかったりします。

第2回では、共通鍵暗号・公開鍵暗号・鍵交換・ハッシュ・署名という暗号の部品を見ました。第3回は手を動かす回です。Linux・MacではOpenSSL 3、Windowsでは標準のPowerShellとcertutilを使って、鍵の作成から自己署名証明書の作成までを試します。

genpkeyで秘密鍵rsa.keyを作り、pkey -puboutで公開鍵rsa.pubを取り出し、test.txtに対して公開鍵で暗号化・秘密鍵で復号・秘密鍵で署名・公開鍵で検証を行うという、この回の手順の全体像を示す図

この回で作るファイルと、使うコマンドの関係は上の図のとおりです。秘密鍵を使うのは「復号」と「署名」、公開鍵を使うのは「暗号化」と「検証」。これを頭に置いて読み進めてください。


OpenSSL 3の基本

まずは、手元のOpenSSLのバージョンを確かめます。

$ openssl version
OpenSSL 3.5.5 27 Jan 2026 (Library: OpenSSL 3.5.5 27 Jan 2026)
$ openssl version -d          # 設定ファイル(openssl.cnf)のある場所
OPENSSLDIR: "/usr/lib/ssl"

OpenSSL 3では、コマンドの書き方がいくつか変わっています。古い手順書を見るときは、この対応を頭に入れておくと混乱しません。

用途以前の書き方OpenSSL 3 での書き方補足
鍵の作成genrsa、ecparam -genkeygenpkeyどのアルゴリズムも同じ書き方。genrsa も動くが、出力は PKCS#8 形式に変わった
鍵の表示・変換rsa、ecpkey古い形式が必要なら rsa -traditional
公開鍵で暗号化・署名rsautlpkeyutlrsautl は 3.0 で非推奨。実行すると警告が出る
鍵を暗号化しない-nodes-noenc-nodes は非推奨(動作はする)
古いアルゴリズムそのまま使えた(MD4、RC4 等)-provider legacy が必要古い PFX を読むときは pkcs12 -legacy
機能の追加エンジン(-engine)プロバイダー(-provider)エンジンは 4.0 で削除された

2026年9月時点でサポートが続くのは3.5(LTS、2030年4月まで)、3.6、4.0などで、1.1.1は2023年9月に、3.0は2026年9月にサポートが終わりました。古いOSのopensslでは一部のコマンドが使えません。

Windowsにはopensslが標準で入っていないため、この記事ではPowerShellとcertutilでの方法を併記します。


鍵を作る(genpkey)

OpenSSL 3では、genpkeyで鍵を作ります。-algorithmで方式を選ぶ、共通の書き方です。

$ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out rsa.key
....+.....+++++(生成の進み具合の表示。略)
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.key
$ openssl genpkey -algorithm ED25519 -out ed.key
$ openssl pkey -in rsa.key -pubout -out rsa.pub     # 公開鍵を取り出す(ec・ed も同様)

$ openssl pkey -in ec.key -text -noout
Private-Key: (256 bit)
priv:
    0b:8c:d7:5c:22:5b:8c:c5:72:c9:2b:cd:24:b3:98:(略)
pub:
    04:80:6a:f4:c5:c6:94:0b:ba:02:0f:a6:95:b4:ee:(略)
ASN1 OID: prime256v1
NIST CURVE: P-256
  • RSAはrsa_keygen_bitsで鍵長(既定は2048)、ECはec_paramgen_curveで曲線を指定する。第2回で見たとおり、これから作るならRSA 3072以上かP-256以上
  • 鍵はPEM形式(-----BEGIN PRIVATE KEY-----で始まるテキスト)で保存される(形式の詳細は第4回)
  • パスフレーズで鍵を守るなら-aes256を付ける。サーバーが自動で読む鍵には付けないことが多く、その分ファイルの権限(600)で守る(OpenSSL 3.5では600で作られた)
  • 公開鍵は配ってかまわないが、秘密鍵はコピーを増やさないのが原則

Windowsには、鍵だけのファイルを作る標準コマンドがありません。鍵は証明書ストアの中に、証明書と一緒に作ります(後で紹介するNew-SelfSignedCertificateや、第6回のcertreq)。


公開鍵で暗号化・復号する(pkeyutl)

第2回で「公開鍵暗号は大きなデータを扱えない」と書きました。本当か、試してみましょう。

$ echo "hello world" > test.txt
$ openssl pkeyutl -encrypt -pubin -inkey rsa.pub -pkeyopt rsa_padding_mode:oaep \
    -pkeyopt rsa_oaep_md:sha256 -in test.txt -out test.enc
$ ls -l test.enc                     # 3072ビットの鍵なので384バイト
-rw-rw-r-- 1 user user 384 Sep 26 10:07 test.enc

$ openssl pkeyutl -decrypt -inkey rsa.key -pkeyopt rsa_padding_mode:oaep \
    -pkeyopt rsa_oaep_md:sha256 -in test.enc
hello world

$ openssl pkeyutl -encrypt -pubin -inkey rsa.pub -pkeyopt rsa_padding_mode:oaep \
    -pkeyopt rsa_oaep_md:sha256 -in big.bin -out big.enc        # 1,000バイトのファイル
Public Key operation error
…:data too large for key size:…(略)
  • pkeyutlはrsautlの後継。パディングはOAEPを指定する(指定しないと古いPKCS#1 v1.5になる)
  • RSAで直接暗号化できるのは鍵長より短いデータだけで、大きなファイルはdata too large for key sizeで失敗する
  • 楕円曲線の鍵では-encrypt自体ができない(鍵交換と署名に使う)

では、大きなファイルはどう暗号化すればよいのでしょうか。WindowsのProtect-CmsMessageが、その答えを見せてくれます。

PS> $cert = New-SelfSignedCertificate -Type DocumentEncryptionCert -Subject "CN=cms-test" -CertStoreLocation Cert:\CurrentUser\My
PS> Protect-CmsMessage -To $cert -Path .\test.txt -OutFile .\test.cms
PS> Unprotect-CmsMessage -Path .\test.cms
hello world
大きくてもよい本文を使い捨てのAESの鍵で暗号化し、そのAESの鍵を相手の証明書の公開鍵(RSA-OAEP)で暗号化して、両方をtest.cmsにまとめるCMSのハイブリッド暗号を示す図

Protect-CmsMessageは、中身をAESで暗号化し、そのAESの鍵を証明書の公開鍵(RSA-OAEP)で暗号化するCMS形式です。RSAが直接暗号化するのは短い鍵だけなので、大きなデータも扱えます。第2回で見たTLSと同じハイブリッドの考え方です。


ハッシュを計算する(dgst/Get-FileHash/certutil)

同じファイルのハッシュ値を、3つの方法で計算してみます。

$ openssl dgst -sha256 test.txt
SHA2-256(test.txt)= a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
$ openssl dgst -sha3-256 test.txt
SHA3-256(test.txt)= a8009a7a528d87778c356da3a55d964719e818666a04e4f960c9e2439e35f138
$ sha256sum test.txt
a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447  test.txt
PS> Get-FileHash .\test.txt | Format-List
Algorithm : SHA256
Hash      : A948904F2F0F479B8F8197694B30184B0D2ED1C1CD2A1EC0FB85D299A192A447
Path      : C:\work\test.txt

PS> certutil -hashfile .\test.txt SHA256
SHA256 ハッシュ (対象 .\test.txt):
a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
CertUtil: -hashfile コマンドは正常に完了しました。
  • OpenSSL 3の表示はSHA2-256(ファイル名)=の形式。-rを付けるとsha256sumと同じ並びになる
  • Get-FileHashの既定はSHA256で、-AlgorithmでSHA384・SHA512などを選べる(SHA-3は選べない)
  • certutil -hashfileはアルゴリズムを省略するとSHA1になる(1行目が「SHA1 ハッシュ」になる)ので、必ず指定する。上は Windows 11(24H2)の日本語版の表示で、Windows Server 2016 では「(ファイル .\test.txt)」、英語版では「SHA256 hash of .\test.txt:」「CertUtil: -hashfile command completed successfully.」と表示される

LinuxとWindowsで値が合わないときは、改行コード(LFとCRLF)やBOMの違いを疑います。見た目が同じでも、バイト列が違えばハッシュ値は変わります(上の例は同じバイト列のファイル)。例えば PowerShell の Set-Content .\test.txt 'hello world' で作ると改行が CRLF になり、SHA256 は 572a95fee9c0f320030789e4883707affe12482fbb1ea04b3ea8267c87a890fb と別の値になります。


署名と検証(dgst -sign/-verify)

次は署名です。1文字だけ書き換えたファイルで、検証が失敗することを確かめます。

$ openssl dgst -sha256 -sign rsa.key -out test.sig test.txt
$ openssl dgst -sha256 -verify rsa.pub -signature test.sig test.txt
Verified OK

$ sed 's/world/World/' test.txt > bad.txt          # 1文字だけ変えたファイル
$ openssl dgst -sha256 -verify rsa.pub -signature test.sig bad.txt
(エラーの詳細の行は略)
Verification failure

$ openssl pkeyutl -sign -inkey ed.key -rawin -in test.txt -out ed.sig          # Ed25519
$ openssl pkeyutl -verify -pubin -inkey ed.pub -rawin -in test.txt -sigfile ed.sig
Signature Verified Successfully
  • dgst -signはハッシュ値を計算して秘密鍵で署名する。検証は公開鍵だけでできるので、誰でも検証できる。1文字でも違えばVerification failure
  • RSA-PSSにするなら、署名と検証の両方に-sigopt rsa_padding_mode:pssを付ける
  • Ed25519はハッシュを内部で計算するため、pkeyutlに-rawinを付けて署名する

Windowsでは、スクリプトのコード署名で同じことを確かめられます。

PS> Set-Content .\test.ps1 'Write-Output "hello"'
PS> $cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=Test Code Signing" -CertStoreLocation Cert:\CurrentUser\My
PS> Set-AuthenticodeSignature -FilePath .\test.ps1 -Certificate $cert
PS> (Get-AuthenticodeSignature .\test.ps1).Status
UnknownError          # 署名は付いたが、発行元(自己署名)を信頼していない
PS> (Get-Content .\test.ps1) -replace 'hello','HELLO' | Set-Content .\test.ps1   # 中身を書き換える
PS> (Get-AuthenticodeSignature .\test.ps1).Status
HashMismatch          # 改ざんを検知
PS> Remove-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)"   # 試し終えたら証明書を消す

UnknownErrorは、発行元を信頼していないだけです。証明書を信頼済みにするとValidになります。そして中身を1語書き換えただけでHashMismatch、つまり改ざんを検知しています。

  • 署名は、ファイルの末尾に # SIG # Begin signature block 〜 # SIG # End signature block のコメントとして付く
  • Add-Content で署名ブロックの後ろに行を足すと、HashMismatch ではなく NotSigned(署名が無い)になる。署名ブロックが末尾に無いと、署名として扱われないため。どちらの場合も、署名を求める実行ポリシー(AllSigned など)ではスクリプトは実行されない
  • Windows Server 2016 と Windows 11(24H2)の Windows PowerShell 5.1 で確かめた結果

鍵交換とHMACを試す

第2回の絵の具のたとえで説明した鍵交換(X25519)も、実際に試せます。

$ openssl genpkey -algorithm X25519 -out alice.key
$ openssl pkey -in alice.key -pubout -out alice.pub
$ openssl genpkey -algorithm X25519 -out bob.key
$ openssl pkey -in bob.key -pubout -out bob.pub

$ openssl pkeyutl -derive -inkey alice.key -peerkey bob.pub | xxd -p -c 32
1e25233c0216702040dc433f8efa24d7dda654f72dae40db1bc1f91e04713442
$ openssl pkeyutl -derive -inkey bob.key -peerkey alice.pub | xxd -p -c 32
1e25233c0216702040dc433f8efa24d7dda654f72dae40db1bc1f91e04713442
aliceとbobがそれぞれX25519の鍵ペアを作って公開鍵だけを交換し、alice.key+bob.pubとbob.key+alice.pubからpkeyutl -deriveで同じ32バイトの値が得られることを示す図

2人がそれぞれ鍵ペアを作り、公開鍵だけを交換します。自分の秘密鍵と相手の公開鍵から計算すると、2人とも同じ32バイトの値になりました。実際のTLSでは、この値をHKDF(HMACを使う鍵の導出)で通信用の鍵に加工します。

HMACも計算してみましょう。

$ openssl dgst -sha256 -hmac "secret" test.txt          # 鍵「secret」で HMAC を計算
HMAC-SHA2-256(test.txt)= 6dc8750717249a7f056dc06f26ff617ae5a7cf18980d4f179657eb93617a553a
PS> $hmac = [System.Security.Cryptography.HMACSHA256]::new([Text.Encoding]::UTF8.GetBytes("secret"))
PS> $mac = $hmac.ComputeHash([IO.File]::ReadAllBytes("$PWD\test.txt"))
PS> -join ($mac | ForEach-Object { $_.ToString("x2") })
6dc8750717249a7f056dc06f26ff617ae5a7cf18980d4f179657eb93617a553a

HMACは、同じ鍵とデータなら誰が計算しても同じ値になり、鍵が違えばまったく別の値になります。Windowsには鍵交換を試す標準のコマンドはありませんが、HMACは.NETのクラスで計算でき、同じバイト列のファイルならopensslと同じ値になります。


自己署名証明書を作る(req -x509/New-SelfSignedCertificate)

最後に、証明書を作ってみます。

$ openssl req -x509 -newkey EC -pkeyopt ec_paramgen_curve:P-256 -noenc \
    -keyout server.key -out server.crt -days 30 -subj "/CN=test.example.jp" \
    -addext "subjectAltName=DNS:test.example.jp,DNS:www.example.jp" \
    -addext "basicConstraints=critical,CA:FALSE" -addext "extendedKeyUsage=serverAuth"

$ openssl x509 -in server.crt -noout -subject -issuer -dates -ext subjectAltName
subject=CN=test.example.jp
issuer=CN=test.example.jp
notBefore=Sep 26 01:07:27 2026 GMT
notAfter=Oct 26 01:07:27 2026 GMT
X509v3 Subject Alternative Name:
    DNS:test.example.jp, DNS:www.example.jp
PS> New-SelfSignedCertificate -DnsName test.example.jp,www.example.jp -CertStoreLocation Cert:\CurrentUser\My `
      -KeyAlgorithm ECDSA_nistP256 -CurveExport CurveName -NotAfter (Get-Date).AddDays(30)

Thumbprint                                Subject
----------                                -------
(40桁の16進数)                          CN=test.example.jp
自己署名証明書server.crtでは発行者(issuer)と主体(subject)が同じで、SAN・basicConstraints CA:FALSE・自分の秘密鍵による署名を持ち、構造はルート証明書と同じであることを示す図
  • 自己署名証明書は、発行者(issuer)と主体(subject)が同じで、自分の秘密鍵で自分に署名したもの。構造はルート証明書と同じ(第5回)
  • -addextで指定しないと、OpenSSLの既定の設定ではCA:TRUE(CAの証明書)として作られる。サーバー用ならCA:FALSEとSANを明示する。ホスト名の検証に使われるのはSAN(CNではない、第4回)
  • New-SelfSignedCertificateは-DnsNameの値をSANに入れ、先頭の名前をSubjectにする。鍵は証明書ストアに作られ、既定の有効期間は1年。ファイルにするにはExport-Certificate・Export-PfxCertificateを使う

自己署名は、検証用・一時的な用途に限ります。第1回で見たとおり、信頼されない証明書の警告に慣れると、本物の攻撃の警告も見過ごしてしまいます。


Windowsでのやり方のまとめ

やりたいことLinux・Mac(openssl)Windows 標準(PowerShell・certutil)補足
鍵ペアを作るopenssl genpkeyNew-SelfSignedCertificate、certreq -new証明書ストアの中に作る
公開鍵で暗号化・復号openssl pkeyutl -encrypt/-decryptProtect-CmsMessage/Unprotect-CmsMessage文書暗号化用の証明書が要る
ハッシュopenssl dgst -sha256、sha256sumGet-FileHash、certutil -hashfile ファイル SHA256Get-FileHash は SHA-3 に非対応
HMACopenssl dgst -hmac、openssl mac.NET の HMACSHA256 クラス専用のコマンドレットはない
署名と検証openssl dgst -sign/-verifySet-AuthenticodeSignature/Get-AuthenticodeSignatureスクリプト・実行ファイルが対象
自己署名証明書openssl req -x509New-SelfSignedCertificate既定の有効期間は1年
証明書の中身を見るopenssl x509 -text -nooutcertutil -dump、[X509Certificate2]第4回で扱う
乱数openssl rand -hex 16.NET の RandomNumberGenerator、Get-SecureRandom(PowerShell 7.4 以降)Get-Random は暗号用ではない

Windowsに標準で入っているWindows PowerShell 5.1を前提にしています(Get-SecureRandomは別に導入するPowerShell 7.4以降だけ)。Git for Windowsに同梱のopenssl.exeを使えばLinuxと同じコマンドも使えますが、バージョンをopenssl versionで確かめてください。


考えてみよう

  1. RSAの公開鍵で大きなファイルを暗号化しようとして失敗しました。大きなファイルを、相手の公開鍵を使って安全に送るには、どう組み合わせればよいでしょうか。
  2. 署名の検証に成功したとき、確かめられたのは「改ざんされていない」ことと、もう1つは何でしょうか。その公開鍵が本当に相手のものかは、どうやって確かめますか。
  3. 職場で、New-SelfSignedCertificateやopenssl req -x509で作った証明書が本番で使われていませんか。期限、SAN、CA:TRUEかどうかを確かめてみてください。

ヒント:この記事のCMS(AESとRSAの組み合わせ)、自己署名証明書のissuerとsubject、第1回の「証明書の役割」が手がかりです。


まとめ

  • OpenSSL 3ではgenpkey・pkey・pkeyutlを使う。rsautlや-nodesは非推奨
  • 秘密鍵は復号と署名、公開鍵は暗号化と検証に使う
  • RSAで直接暗号化できるのは短いデータだけ。大きなデータはAESとの組み合わせ(CMS・TLS)
  • certutil -hashfileはアルゴリズムを必ず指定。値が合わないときは改行コードを疑う
  • 自己署名証明書はCA:FALSEとSANを明示して、検証用に限って使う

次回は、ここで作った証明書の中身(X.509)を、SAN・拡張・ファイル形式まで読み解きます。


連載「証明書入門」全11回

  1. なぜ証明書が必要か
  2. 暗号の基礎
  3. openssl・PowerShellで試す(この記事)
  4. X.509証明書の中身
  5. PKIと信頼の仕組み
  6. 証明書の種類と発行
  7. TLSの仕組み
  8. ライフサイクルと自動化
  9. 社内PKIとクラウド
  10. 証明書トラブルシュート
  11. 証明書の設計演習

コメント

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