RSA署名検証って何をどうしてるの?


この記事はRubyのOpenSSLライブラリを使ってRSA署名検証がどのように行われるのか疑問に思いコードを書いてみた備忘録のようなものです。
公開鍵暗号やRSA暗号について詳しく踏み込むような内容ではありません。

公開鍵暗号方式の署名という行為はどうやらメッセージを秘密鍵で暗号化したものであるということみたいです。
(RSA暗号での署名はメッセージを秘密鍵で 復号化 するというのが正しいという話もあります。RSAの産みの親がそう言っていたのだとか。)
ここでは署名検証は署名を公開鍵で 復号化(public_decrypt) して確認するので署名は秘密鍵で 暗号化 という定義にしておきます。

それでは、Rubyを使って署名の作成と署名検証を行っていきます。

require 'openssl'

# 鍵の作成
rsa = OpenSSL::PKey::RSA.generate 2048

# メッセージ
M = "署名検証手順確認"

# ハッシュ関数指定
digest = OpenSSL::Digest::SHA256.new

# 署名作成
sig = rsa.sign(digest, M)
=> "#I\xE1\xE9\xE3\x95\xCE\xE2U\x8EwU\xF49a\f\xE7\x11\xF5\xC5|H\xF0\xB1\x9Ct\xE5\xB3\x93Tj\xD2\xFC,\x0F1+\xB9\x88p\xEEe\x99#\xCC\xB3\x81\x98U\x01Uc\xC3*\x92\v\xD3(\xE9~&@{\xA1S\x83\v\x83\xFF\xDF\xB8\x82r\xC8\x85|\xC1^\x9F>\xDAc\x17\x9A\xEB\xA8\x1E\xA8\xA4v\xC0\xA3\x98f\xFF\x87^\xFB9#\x98l\xE1\xA9\xB5g-\a\xC3\xAD\x17&\x8C\x84\xAD\x06\xB7\x04c\xA9\xB4{w\x15\xDB\f\xCFQ\x91\xF8t\x16\x8A\x8A\xBC\xB2\xC5H\xD1\xC8p}\xC7\xD6\a\x0F'm\xDB\tT\xDF4\xAAv\xF1\xD4\x14\x86\xD0\x82?\xA6\xB8\xC8\x91\xA8Su\x81Yc9\x83\x94$\x96I\xC9%\xE3\x82\xD6\xB6j\xD1\xB9\xDB\xE0\xD80=v\xBD\n\xDC\xFB:\xC9\x01\xA6\xF3\xC2\xBAT\xAE\x98\xE7B\x10\xE7$\x12_\xEC\x1Ar\x86B\xDEC<:nfV\x12z\xC8%Ng\xF0\xCF\xAA\xD2\x94\xC1\xC0\x1C\x9D,>\xF7+\x83s\xCBX)!\x90)W\xF0\xEA"

# 署名検証
rsa.verify(digest, sig, M)

上記で署名の作成と検証ができています。

・・・
・・・

検証って一体何してるの?
って自分は思いました。

こちらの記事と記事中にあるブログの画像を参考にさせていただきました。

署名検証は
- メッセージをハッシュ関数でハッシュ値H1を出す
- 署名を公開鍵で復号化しEMSA-PKCS1-v1_5のデータを得る
- EMSA-PKCS1-v1_5データのパディングを除去してH2を得る
- H1とH2を比較する

という手順を踏めばRubyのOpenSSLでは署名検証出来そうです。(本当は参考記事のようにさらに処理をしないといけないのかなと思います。)

実際にコードで実験してみます。

# 署名を復号化
decrypted = rsa.public_decrypt(sig)
=> "010\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\x04 Un\x1A\x02^\xDE\x11Q\x9C~\xD3c\xE7H}6\x88\xAF\x1E\xC5\xAC7#\xB6\xC4@\"_]\xB9W\x10"

# 2048bit鍵なので32byteを取り出す
m_hash = decrypted[-32, 32]
=> "Un\x1A\x02^\xDE\x11Q\x9C~\xD3c\xE7H}6\x88\xAF\x1E\xC5\xAC7#\xB6\xC4@\"_]\xB9W\x10"

# メッセージのハッシュ値
digest.digest(M)
=> "Un\x1A\x02^\xDE\x11Q\x9C~\xD3c\xE7H}6\x88\xAF\x1E\xC5\xAC7#\xB6\xC4@\"_]\xB9W\x10"

m_hash == digest.digest(M)
=> true

署名を復号化したものと、メッセージをハッシュしたものが一致しました。
検証成功ですね。

検証失敗パターンも確認しておきましょう。

m_hash == digest.digest("検証失敗するよ")
=> false

想定通り失敗しました。

署名検証 verify の中ではこのような処理が行われているようです。

僕と同じ疑問を持った方の参考になれば幸いです。

この領域に強い方のご指摘や修正リクエスト大歓迎です。