C#で作った署名をOpenSSLで検証してみる
はじめに
Writeup : 2020年に修正されたとある脆弱性についてという記事の中にあるC#のコードを読んでいると、次のような部分がある。
//メッセージをバイト型配列にして、SHA1ハッシュ値を計算 var msgData = Encoding.UTF8.GetBytes(message); var hashData = (new SHA1Managed()).ComputeHash(msgData); //RSACryptoServiceProviderオブジェクトの作成 var rsa = new RSACryptoServiceProvider(); //秘密鍵を使って初期化 rsa.FromXmlString(privateKey); //RSAPKCS1SignatureFormatterオブジェクトを作成 var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); //署名の作成に使用するハッシュアルゴリズムを指定 rsaFormatter.SetHashAlgorithm("SHA1");
このように最初にSHA1を計算したあとにrsaFormatter.SetHashAlgorithm("SHA1")
のようにSHA1
をもう一度指定しているので、これはSHA1の二重ハッシュに署名してしまうのではないか?という疑問が生じたため、このコードを多少改造しつつOpenSSLコマンドを用いて、どのようなデータに署名されているのかを明らかにしてみることとした。
TL; DR
二重ハッシュではなく、1回だけのSHA1が行なわれる。
実験
C#コード
記事と同様にSHA1を利用してhello world!
に署名するプログラムを作成した。
ただし、このプログラムはオリジナルと比べて次のような相違点がある。
- 全体的に下記のMSのドキュメンテーションをベースにした
- 元々のプログラムは外からRSAの公開鍵・秘密鍵を取得しているが、ここでは適当な鍵ペアを生成してそれを
Console.WriteLine
で標準出力することにした- そのとき
rsa.ToXmlString
関数で鍵をXMLとして出力した
- そのとき
- 署名ずみバイナリーデータはBase64してこれも
Console.WriteLine
で標準出力に書き出すこととした - なぜかSHA1を利用すると次のようなエラーで
openssl
による署名検証が失敗してしまうので、SHA256で代替した$ openssl rsautl -verify -asn1parse -in sign.bin -certin -inkey pubkey.pem -pubin RSA operation error 4620246528:error:0407008A:rsa routines:RSA_padding_check_PKCS1_type_1:invalid padding:crypto/rsa/rsa_pk1.c:67: 4620246528:error:04067072:rsa routines:rsa_ossl_public_decrypt:padding check failed:crypto/rsa/rsa_ossl.c:588:
- 利用した
openssl
のバージョンはOpenSSL 1.1.1i 8 Dec 2020
である
- 利用した
このプログラムは次のような出力を返す。
Public Key:
<RSAKeyValue><Modulus>wzCS7AYAnfIRFY6i2Tpz2J6K2BNjqJpb5VvtbnCjcnzKzyp1gTl9rNvFzFQZQ7xUOpdkQsDVx4b+G5bKzdbnL4dDvunCheLgsQNdO99V0IrTYxCFClOcgOOGyoFC+QLqqQ2d4dRoKulq69uT+eGiwV4FXlZG7Pwu0nksc3mlJ7s=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
Private Key:
<RSAKeyValue><Modulus>wzCS7AYAnfIRFY6i2Tpz2J6K2BNjqJpb5VvtbnCjcnzKzyp1gTl9rNvFzFQZQ7xUOpdkQsDVx4b+G5bKzdbnL4dDvunCheLgsQNdO99V0IrTYxCFClOcgOOGyoFC+QLqqQ2d4dRoKulq69uT+eGiwV4FXlZG7Pwu0nksc3mlJ7s=</Modulus><Exponent>AQAB</Exponent><P>9KGnwrMYbNJGks9D99bKx/kQysZs0Tdj2uM6uBiU1k9xWaOqzUiLaZB4gch2G+1OV1R2bKuQEz2WyeQyvRIU6Q==</P><Q>zEK3uD7Dw+ZrWeezqPolIULybgF0e2Mq+Jr7E/+CdSV7AWtccRyCZujxEesyQ0fj2/yteIT2Uo7k3PC/3UoBAw==</Q><DP>BLT7kl550NY272o3h5RFcJWVQiGRRHFJZZPLtHEcpAcBSlVA2xRTQmO6Pd0KkLz/LeT9JlgivIwJ07alV0f6yQ==</DP><DQ>w/OROiCEP2/SNoqQER//9Lu7xIqCy0fkVmCfU5z/8xAEw+TR5vUZqE35zl3ady8FSepKJF8xyxuoNMiE126CLw==</DQ><InverseQ>V9W4AWyN1Pd2dk1Tx2qEiCImomDQhFaiBCBONrLBT5xS8pOiLotTrXkv3XiNTQCKJ+68+a0PbgHUoYSzoDGEKw==</InverseQ><D>OeU7fxSctDyrwpgnR4Wl/PexuTuvEMCQR2zH9T0lzfyj72Tpq6XQ2Cfr+JptUAEZfrOPApnODzvEPYyxpPJ8acBwY48sB1mXsXsXYCCnKzXi/4OvUO8KLbGByhIeoQrYulrAMqfSTpwqbnlBWQv10URaZwdKHK24h4YmfEw+kTE=</D></RSAKeyValue>
Sign:
Zv5AzbGtV+2h79SazGUz68GuiLSc1wpSxPJmZ6LWiuEXfUEXuMrK8EROBeqAhaMPN+APVHJhRtjJyWvaVywRuYavqFL99iukE7+hvLCcCpV2PKQPb/RLFs0ekW3982F2gNvbGw+nGaDPqtSjfJgohWdjmj0JYNSNMfDnhr+XzEY=
このうち署名データ(Sign
)を次のようにsign_sha256.bin
という名前で保存する。
echo "Zv5AzbGtV+2h79SazGUz68GuiLSc1wpSxPJmZ6LWiuEXfUEXuMrK8EROBeqAhaMPN+APVHJhRtjJyWvaVywRuYavqFL99iukE7+hvLCcCpV2PKQPb/RLFs0ekW3982F2gNvbGw+nGaDPqtSjfJgohWdjmj0JYNSNMfDnhr+XzEY=" | base64 -D > sign_sha256.bin
公開鍵データのPEM化
検証のためには公開鍵をOpenSSLに入力する必要があるが、上記のように鍵データはXML形式となっているためRSA Key Converterで変換し次のような公開鍵のPEMデータを得た。
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDMJLsBgCd8hEVjqLZOnPYnorY
E2OomlvlW+1ucKNyfMrPKnWBOX2s28XMVBlDvFQ6l2RCwNXHhv4blsrN1ucvh0O+
6cKF4uCxA10731XQitNjEIUKU5yA44bKgUL5AuqpDZ3h1Ggq6Wrr25P54aLBXgVe
Vkbs/C7SeSxzeaUnuwIDAQAB
-----END PUBLIC KEY-----
このデータをpubkey_sha256.pem
という名前で保存して、openssl
コマンドでデータを表示すると次のようになる。
$ openssl pkey -in pubkey_sha256.pem -pubin -text -----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDMJLsBgCd8hEVjqLZOnPYnorY
E2OomlvlW+1ucKNyfMrPKnWBOX2s28XMVBlDvFQ6l2RCwNXHhv4blsrN1ucvh0O+
6cKF4uCxA10731XQitNjEIUKU5yA44bKgUL5AuqpDZ3h1Ggq6Wrr25P54aLBXgVe
Vkbs/C7SeSxzeaUnuwIDAQAB
-----END PUBLIC KEY-----
RSA Public-Key: (1024 bit)
Modulus:
00:c3:30:92:ec:06:00:9d:f2:11:15:8e:a2:d9:3a:
73:d8:9e:8a:d8:13:63:a8:9a:5b:e5:5b:ed:6e:70:
a3:72:7c:ca:cf:2a:75:81:39:7d:ac:db:c5:cc:54:
19:43:bc:54:3a:97:64:42:c0:d5:c7:86:fe:1b:96:
ca:cd:d6:e7:2f:87:43:be:e9:c2:85:e2:e0:b1:03:
5d:3b:df:55:d0:8a:d3:63:10:85:0a:53:9c:80:e3:
86:ca:81:42:f9:02:ea:a9:0d:9d:e1:d4:68:2a:e9:
6a:eb:db:93:f9:e1:a2:c1:5e:05:5e:56:46:ec:fc:
2e:d2:79:2c:73:79:a5:27:bb
Exponent: 65537 (0x10001)
このように正常なPEM形式のデータとなったことが確認できる。
署名データの検証
openssl
コマンドで次のようにする。
$ openssl rsautl -verify -asn1parse -in sign_sha256.bin -certin -inkey pubkey_sha256.pem -pubin
0:d=0 hl=2 l= 49 cons: SEQUENCE
2:d=1 hl=2 l= 13 cons: SEQUENCE
4:d=2 hl=2 l= 9 prim: OBJECT :sha256
15:d=2 hl=2 l= 0 prim: NULL
17:d=1 hl=2 l= 32 prim: OCTET STRING
0000 - 75 09 e5 bd a0 c7 62 d2-ba c7 f9 0d 75 8b 5b 22 u.....b.....u.["
0010 - 63 fa 01 cc bc 54 2a b5-e3 df 16 3b e0 8e 6c a9 c....T*....;..l.
出力の最後の2行が署名されたハッシュ値になるので、hello world!
のハッシュ値と比べることでこれが二重なのかそうでないのか明らかにできる。
$ echo -n 'hello world!' | shasum -a 256
7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9 -
上記のとおり1回だけSHA256のハッシュ関数に投入したデータと一致するため、これは1回だけのハッシュ結果を利用している。
まとめ
結果としては二重ハッシュとはなっていなかった。おそらくrsaFormatter.SetHashAlgorithm("SHA1")
の部分はPKCS1に含まれるのメタ情報のハッシュアルゴリズムやパディングのために保存していると思われる。
Discussion