Open-SSLで作ったCSRファイルの電子署名を解析してみた

# はじめに
ある日偉い人から「Open-SSLで作った証明書署名要求(CSR)の中にある電子署名の仕組みを説明してね」と言われたため色んなサイトを巡って電子署名の勉強をしました。
折角学んだ事なのでここに備忘録として残しておきます。

# 電子署名とは
電子文書に貼り付ける電子的な微証(ちょうしょう)です。
電子書名は本人の確認だけでなく電子文書の偽造・改竄(かいざん)の検出も出来ます。
詳しい事は[Wipedia][0]を見て

# 電子署名の仕組み
電子署名を実現する仕組みとしては公開鍵暗号方式に基づくデジタル署名が有力です。
処理の流れは以下の通り。

__送信側__
![send.jpg](https://qiita-image-store.s3.amazonaws.com/0/112253/852e7715-47da-c6cd-a01f-1b2ed527d836.jpeg)  

__受信側__
![recieve.jpg](https://qiita-image-store.s3.amazonaws.com/0/112253/574f78d6-4f3d-52c1-d411-2ce8a99a1022.jpeg)

# CSRの電子署名の仕組み
図にするとこんな感じ。

![test.PNG](https://qiita-image-store.s3.amazonaws.com/0/112253/61fc460c-a9d0-5e64-b83a-8c9f98ee4b52.png)


実際の検証については以下の通り

# 検証
open-sslでファイルを作って中身を検証してみます。
CSRのファイルの構成を見る為に必要なファイルを作ります。

[OS] Windows 7 pro 32bit
[Open-SSL] OpenSSL 1.0.1i 6

## 秘密鍵の生成
512ビット長のRSA暗号で秘密鍵を作ります。

```
# openssl genrsa -out pv.key 512
```

## 公開鍵の生成
秘密鍵を使って公開鍵を作ります。

```
# openssl rsa -in pv.key -pubout -out pub.key
```

## 証明書署名要求(CSR)の生成
秘密鍵を使ってCSRファイルを作ります。
入力を要求される項目はEnterキーを押して全てスルーします。

```
# openssl req -new -key pv.key -out csr.csr

# Country Name (2 letter code) [AU]:
# State or Province Name (full name) [Some-State]:
# Locality Name (eg, city) []:
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:
# Organizational Unit Name (eg, section) []:
# Common Name (e.g. server FQDN or YOUR name) []:
# Email Address []:
# 
# Please enter the following 'extra' attributes
# to be sent with your certificate request
# A challenge password []:
# An optional company name []:
```

## 証明書署名要求(CSR)の形式を変更
CSRファイルがPEM形式の場合はDER形式に変換します。

```
# openssl req -inform PEM -in csr.csr  -outform DER -out csr.csr
```

これで必要なファイルは全て揃いました。


# 証明書署名要求(CSR)の構成
早速CSRファイルの中身をのぞいてみます。

```
# openssl req -text -inform DER -in csr.csr -text -noout
#
# --------------------------------------------------------------------
# Certificate Request:
#     Data:
#         Version: 0 (0x0)
#         Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (512 bit)
#                 Modulus:
#                     00:d1:e0:0a:11:a7:73:b9:0b:51:a1:f7:46:20:3f:
#                     a7:16:87:ff:e1:7e:d9:9f:2b:91:e2:d5:d0:bd:53:
#                     96:36:d4:04:73:9c:4a:c7:33:03:11:29:f9:31:2f:
#                     57:32:bc:b5:72:05:df:c1:f9:07:6f:bb:e9:f9:ff:
#                     43:b4:24:c1:17
#                 Exponent: 65537 (0x10001)
#         Attributes:
#             a0:00
#     Signature Algorithm: sha1WithRSAEncryption
#          4c:24:b8:d3:61:94:13:88:b1:5a:48:96:54:0c:61:ce:23:02:
#          f2:95:bf:50:02:e0:a2:a6:5b:e6:62:c9:57:f4:f7:e2:df:37:
#          b8:0b:23:fe:e8:bf:41:7e:08:66:3a:47:74:d1:ce:be:71:1b:
#          88:07:b9:15:92:4b:2d:78:63:f7
# --------------------------------------------------------------------
```
今回はこの様に出力されました。
上記の部分で署名に当たるのは Signature Algorithm の __4c:24:b8 … 78:63:f7__  の部分です。
該当箇所をバイナリエディタで抽出して保存します(csr.sinとしました)

# 電子署名の復号
さて前述の通りこの電子署名と言うものが「__平文をハッシュ値化してそれを秘密鍵でRSA暗号化_」したものならば公開鍵で復号できるはずです。
事前に作成していた公開鍵で復号してみましょう。

```
# openssl rsautl -verify -in csr.sin -pubin -inkey pub.key -out csr.hs
#
# --------------------------------------------------------------------
# 30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14 F4
# 24 E3 1B A0 B1 8E F3 3D 5D 8A 48 CF A7 1B 51 CE
# 2D AF 28
# --------------------------------------------------------------------
```

これがハッシュ値？ではなさそうですね。
CSRファイルの中身にはこう記述されてます。  
`> Signature Algorithm: sha1WithRSAEncryption` これはSHA-1でハッシュ値にした値をRSAで暗号化してると言う意味です。
SHA-1は平文を 160bit(20byte) のハッシュ値にするアルゴリズムなので 35byte もあるのはどう考えてもおかしい…。

はて？

# 復号したデータの正体
暫く考えてみると何か規則性がある並びである事に気づきます。よく見るとこれはANS.1形式のデータの様です。 
 [ANS.1形式を解析してくれるサイト様][1]を頼りに上記バイナリを解析して貰うと…

```
SEQUENCE {  
   SEQUENCE {  
      OBJECTIDENTIFIER 1.3.14.3.2.26  
      NULL  
   }  
   OCTETSTRING F424E31BA0B18EF33D5D8A48CFA71B51CE2DAF28  
}
```

上記の結果が得られました。
その中をよく見ると、これ！

> OCTETSTRING F424E31BA0B18EF33D5D8A48CFA71B51CE2DAF28

20バイトの16進数文字列を発見しました。
間違いなくこの値が平文をハッシュ値化した値だと確信できました。
よかった、よかった。

# 平文からハッシュ値を求める
しかしこれで終わりでは無く、このハッシュ値を元となった平文がどこかを探す必要があります。
生憎、暗号化と違いハッシュ値から元のデータを得る事は出来ません。
つまりここから先は得られたハッシュ値を頼りに心当たりのある平文をSHA1でハッシュ値化して検証するしかない様です。

漠然と探してもしょうがないので仕様書から情報を探してみます。
すると __RFC2986__ に署名データの成り立ちが記載されていました。  

> The signature process consists of two steps:
> 1. The value of the certificationRequestInfo component is DER encoded, yielding an octet string.
> 2. The result of step 1 is signed with the certification request subject's private key under the specified signature algorithm, yielding a bit string, the signature.

上記の仕様を要約すると

1. certificationRequestInfo 構成する値(16進数文字列)をDERにエンコードして署名アルゴリズムを使ってハッシュ値化
2. ハッシュ値化したデータを秘密鍵を使って署名(暗号化)する

と言う事の様です。

同じ仕様書内でcertificationRequestInfoを検索すると

```
CertificationRequestInfo ::= SEQUENCE {  
    version Version,  
    subject Name,  
    subjectPublicKeyInfo SubjectPublicKeyInfo,  
    attributes [0] IMPLICIT Attributes  
}  
```

これはCSRファイルの内容に当てはめると

```
> Certificate Request:  
>    Data:  
>        Version: 0 (0x0)  
>        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd  
>        Subject Public Key Info:  
>            Public Key Algorithm: rsaEncryption  
>                Public-Key: (512 bit)  
>                Modulus:  
>                    00:d1:e0:0a:11:a7:73:b9:0b:51:a1:f7:46:20:3f:  
>                    a7:16:87:ff:e1:7e:d9:9f:2b:91:e2:d5:d0:bd:53:  
>                    96:36:d4:04:73:9c:4a:c7:33:03:11:29:f9:31:2f:  
>                    57:32:bc:b5:72:05:df:c1:f9:07:6f:bb:e9:f9:ff:  
>                    43:b4:24:c1:17  
>                Exponent: 65537 (0x10001)  
>        Attributes:  
>            a0:00  
>    Signature Algorithm: sha1WithRSAEncryption  
>         4c:24:b8:d3:61:94:13:88:b1:5a:48:96:54:0c:61:ce:23:02:  
>         f2:95:bf:50:02:e0:a2:a6:5b:e6:62:c9:57:f4:f7:e2:df:37:  
>         b8:0b:23:fe:e8:bf:41:7e:08:66:3a:47:74:d1:ce:be:71:1b:  
>         88:07:b9:15:92:4b:2d:78:63:f7  
```

先頭からa0:00までの範囲になります。
この範囲をDERに直すと以下のようになります。

```
30 81 AA 02 01 00 30 45 31 0B 30 09 06 03 55 04
06 13 02 41 55 31 13 30 11 06 03 55 04 08 0C 0A
53 6F 6D 65 2D 53 74 61 74 65 31 21 30 1F 06 03
55 04 0A 0C 18 49 6E 74 65 72 6E 65 74 20 57 69
64 67 69 74 73 20 50 74 79 20 4C 74 64 30 5C 30
0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 03 4B
00 30 48 02 41 00 D1 E0 0A 11 A7 73 B9 0B 51 A1
F7 46 20 3F A7 16 87 FF E1 7E D9 9F 2B 91 E2 D5
D0 BD 53 96 36 D4 04 73 9C 4A C7 33 03 11 29 F9
31 2F 57 32 BC B5 72 05 DF C1 F9 07 6F BB E9 F9
FF 43 B4 24 C1 17 02 03 01 00 01 A0 00
```
上記の値をSHA-1でハッシュ化にすると署名と同様のハッシュ値が得られるはずです。  
ハッシュ値化プログラムを使ってハッシュ値に変換すると…。  

>Hash=__f424e31ba0b18ef33d5d8a48cfa71b51ce2daf28__

おお！！期待する値が返ってきました。 
これで間違い無かった様です。  

# まとめ
今回の事で以下の2点が分かりました。

+ CSRファイルの電子署名に使われている平文ってどこの事？
+ どうやって署名している？

メールや他のデータで電子署名を使うときも同じ用にして使われていることが分かりました。
またこれにより署名の必要性についても理解出来ました。  


署名は秘密鍵を使って作られる。
秘密鍵は発行者のみが持っている情報なので発行元により署名された（成りすましではない）ファイルであることが保証される。

またCSRファイルその物が悪意ある第三者の手によって改ざんされていた場合、署名がなければ改ざんされたどうかもわからないが、
署名があれば__署名が復号出来ない__ もしくは __ハッシュ値が一致しない__ などの影響がある為、そのファイルに問題があるかどうかがすぐに分かる。

Open-sslとして提供されているので、内部の事を知りたくて調べる人はあまりいないと思いますが、
せっかく調べたので共有できればいいと思ってエントリーしました。
誰かの役に立ったのなら幸いです。


[0]: https://ja.wikipedia.org/wiki/%E9%9B%BB%E5%AD%90%E7%BD%B2%E5%90%8D
[1]: http://www.geocities.co.jp/SiliconValley-SanJose/3377/