C#で作った署名をOpenSSLで検証してみる

6 min read読了の目安(約6000字

はじめに

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データを得た。

pubkey_sha256.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に含まれるのメタ情報のハッシュアルゴリズムやパディングのために保存していると思われる。