38
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

マイナンバーカード検証#2 - 利用者証明用電子証明書

Last updated at Posted at 2019-01-05

はじめに

マイナンバーカードの検証の話です。
#1からの続きです。

利用者証明用電子証明書に関するFILEを検証します
公開鍵の取得、秘密鍵で署名、署名の検証です。

目次

1.認証用証明書の取得
2.認証用証明書から公開鍵を取り出す
3.署名
4.検証

image.png

1.認証用証明書の取得

認証用証明書をSELECTして証明書を読み出します。この証明書はPINのロックがないので簡単です。
以下のおまじない(APDU)をSCardTransmitで送信していきます。

##(1)SELECT FILE 公的個人認証AP

  • APDU = 0x00, 0xA4, 0x04, 0x0C, 0x0A, 0xD3, 0x92, 0xF0, 0x00, 0x26, 0x01, 0x00, 0x00, 0x00, 0x01
  • Response = 0x90, 0x00 ← 正常終了という意味

##(2)SELECT FILE 認証用証明書

  • APDU = 0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x0A
  • Response = 0x90, 0x00

##(3)READ BINARY

認証用証明書へのSELECT FILEが成功したら証明書の読み出し(READ BINARY)となります。
証明書はDER形式のバイナリデータで私のカードの場合1,583byteありました。
READ BINARYのポイントは二つ

  • 最初にDER証明書全体のサイズを知る必要がある。
  • 1回のREAD BINARYは256バイトまでしかできない。

DER証明書全体のサイズを求める

DER証明書はASN.1という形式になっており、これが[Tag]-[Length]-[Value]構造の繰り返しになっています。なので、まずは、LengthをGETしてDER証明書全体のサイズを求めます。
結論を言うと最初の4byteをREADすればおk。

  • APDU = 0x00, 0xB0, 0x00, 0x00, 0x04
  • Response = 0x30, 0x82, 0x06, 0x2B, 0x90, 0x00

APDUの0x04が4byte READ する、ということです。
Responseの後方0x90, 0x00が成功した、という意味で、0x30, 0x82, 0x06, 0x2BがREADした4byteのBINARYです。

READしたBINARYをANS.1の形式でパースします。この部分は[Tag]-[Length]の部分。

0x30, 0x82, 0x06, 0x2B

  • 0x30 = Tag っていう意味

  • bitにすると0011-0000

    • b8-b7 = 00
      • クラス = 汎用 っていう意味
    • b6 = 1
      • 構造化フラグ = 構造型
    • b5-b1 = 0x10
      • タグ番号 = SEQUENCE(ASN.1 オブジェクトの集合を表記するための型)
  • 0x82 = Length

  • bitにすると1000-0010

    • b8 = 1
      • Lengthが128オクテット(byte)以上ある
      • ここが0だと、この1byteの残り7bitでLentghとなる
    • b7-b = 0x02
      • 長さ部の長さ = 2byte
      • ここが3以上になることは無い という決めつけをする
  • 0x06, 0x2B = Length

    • Big Endian の short型の数値
    • ということで 1579 byte

1回のREAD BINARYは256バイトまでしかできない

ブロックサイズが256byteでブロック単位でしか読めないというきわめてハード的なお約束があります。
なんで、何回かに分けてREADします。READするブロックNoを指定するため、APDUを少し変えて何発も送信する必要があります。

  • Block0
    • APDU = 0x00, 0xB0, 0x00, 0x00, 0x00
    • Response = …256byte…, 0x90, 0x00
  • Block1
    • APDU = 0x00, 0xB0, 0x01, 0x00, 0x00
    • Response = …256byte…, 0x90, 0x00
  • Block2
    • APDU = 0x00, 0xB0, 0x02, 0x00, 0x00
    • Response = …256byte…, 0x90, 0x00

・・・

APDUの3byte目に0~のブロック番号を指定します。こんな感じで256byteずつREADしていきます。
こうしてDER形式の証明書をGETします。

ちゃんとGETできたかどうか、確認したいですね。
OpenSSLで証明書の中身を見ることができます。

コマンドプロンプト

・・・READ_BINARYしたデータをAuth_Cert.derというファイルで保存しておく

c:\work>openssl x509 -in Auth_Cert.der -inform DER -out Auth_Cert.pem -outform PEM
c:\work>openssl x509 -text -fingerprint -noout -in Auth_Cert.pem

・・・証明書の中身が人間にもわかる形式でダラダラと表示されればOK・・・

#2.認証用証明書から公開鍵を取り出す
署名検証では公開鍵が必要になります。公開鍵は先ほどGETした証明書の中に入っています。
証明書の中から公開鍵だけを取り出さねばなりません。

C#ではSystem.Security.Cryptography.X509Certificates.X509Certificate2クラスのGetPublicKeyメソッドで簡単に公開鍵を取り出すことができます。
しかしトラップがあります。

GetPublicKeyでGETした公開鍵はOpneSSLでは取り扱うことができません。
GetPublicKeyメソッドではPKCS#1形式という形式で取り出すようで、OpenSSLで取り扱うにはPKCS#8形式でないとダメなようです。これは非常に不便です。

PKCS#1⇒PKCS#8変換の方法を調べてWebを彷徨いましたがよい方法が見つからず、あきらめて思考停止手段を用いることにしました。

PKCS#1とPKCS#8の公開鍵のDiffをとって差分を固定で埋める。

すなわちPKCS#1のデータに
30820122300d06092a864886f70d01010105000382010f00
という謎のヘッダを付加する。
これでうまくいく。なぜうまくいくかは知らない。


List<byte> pubkey_pkcs8 = new List<byte>();
var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(certDER);
byte[] pubkey_pkcs1 = x509.GetPublicKey();

// ここで取れるpubkey_pkcs1はPKCS#1形式の公開鍵
// 先頭に
// 30820122300d06092a864886f70d01010105000382010f00
// を付加するとOpenSSLで取り扱い可能なPKCS#8になる

pubkey_pkcs8.AddRange(Common.HexStringToBytes("30820122300d06092a864886f70d01010105000382010f00").ToArray());
pubkey_pkcs8.AddRange(pubkey_pkcs1.ToArray());

byte[] publickeyDER = pubkey_pkcs8.ToArray();

ソースはこちら

Webを彷徨っていてみつけた手掛かり⇒RSA公開鍵のファイル形式とfingerprint

ちなみにOpenSSLで DER形式 のPublicKeyを確認するコマンド

PublicKeyをOpenSSLで見るコマンドDER形式の場合

・・・READ_BINARYしたデータをAuth_PublicKey.derというファイルで保存しておく

c:\work>openssl rsa -pubin -in Auth_PublicKey.der -text -noout -inform der

Public-Key: (2048 bit)
Modulus:
    00:8f:e0:2a:24:45:51:40:77:b0:01:fa:01:48:fb:
    ・・・
ってかんじで表示されればOK

ついでに PEM形式 の場合

PublicKeyをOpenSSLで見るコマンド【PEM形式の場合】
c:\work>openssl rsa -pubin -in Auth_PublicKey.pem -text -noout

Public-Key: (2048 bit)
Modulus:
    00:8f:e0:2a:24:45:51:40:77:b0:01:fa:01:48:fb:
    ・・・
ってかんじで表示されればOK

3.署名

『とある電子データについての電子署名を作成する』ことです。マイナンバーカードではそれができるのです。自分のハンコを押すみたいなものです。
電子署名に自分の個人情報が入ることはありませんので安心してください。

今さっき「マイナンバーカードでは電子署名を作成することができる」としましたが、厳密には間違いです。
マイナンバーカードでできるのは、「与えられたデータを秘密鍵で暗号化する」ことだけです。
認証用鍵にSELECT FILEしてからCOMPUTE DIGITAL SIGNATUREコマンドで認証用鍵で暗号化します。
ただし、認証用鍵にアクセスするにはPINロックの解除が必要です。先にPINロックを解除してから認証用鍵にSELECT FILEしましょう。

##(1)SELECT FILE 公的個人認証AP

  • APDU = 0x00, 0xA4, 0x04, 0x0C, 0x0A, 0xD3, 0x92, 0xF0, 0x00, 0x26, 0x01, 0x00, 0x00, 0x00, 0x01
  • Response = 0x90, 0x00

##(2)SELECT FILE 認証用PIN

  • APDU = 0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x18
  • Response = 0x90, 0x00

(3)VERIFY 認証用PIN

※PINが1234の場合

  • APDU = 0x00, 0x20, 0x00, 0x80, (※1)0x04, (※2)0x31, 0x32, 0x33, 0x34
  • Response = 0x90, 0x00

※1=PINのバイト数、※2=PINデータを指定します。
成功するとPINロックが解除されて認証用鍵にアクセスできるようになります。
PINが間違っている場合はResponse=0x63, 0xC?といった値が返ってきます。このとき?にロックまでのリトライ回数が返ってきます。

認証用PINのリトライは3回です。3回間違うとロックされてしまいます。復旧は役所に行って申請書書かないといけいないので注意してください。
ちなみに認証成功するとまたリトライが3回に戻ります。

リトライ回数だけ知りたいですね。その場合はPINデータをセットしないでVERIFYします。

  • APDU = 0x00, 0x20, 0x00, 0x80
  • Response = 0x63, 0xC3

この場合リトライ回数を減らさずにリトライ回数がGETできます。

(4)SELECT FILE 認証用鍵

  • APDU = 0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x17
  • Response = 0x90, 0x00

(5)COMPUTE DIGITAL SIGNATURE

このコマンドでデータを暗号化します。
直前にSELECT FILEしている認証用鍵で暗号化します。

  • APDU = 0x80, 0x2A, 0x00, 0x80, (※1), (※2)
  • Response = (※3), 0x90, 0x00

※1=対象データのバイト数(1byte)、※2=対象データを指定します。
※3=暗号化されたデータ

電子署名について

COMPUTE DIGITAL SIGNATUREで暗号化しますが、いわゆる電子署名にするにはPKCS #1 (RFC 3447)の仕様の通りにする必要があります。
これがまためんどくさいのですが、仕方ありません。以下の手順でRFCに準拠したDigestInfoを作成します。

①電子署名を作成するデータのSHA1を求める

byte[] digest = null;
using (var fs = new System.IO.FileStream(targetFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) {
    digest = System.Security.Cryptography.SHA1.Create().ComputeHash(fs);
}

②ASN.1 DigestInfoを作成

DigestInfoなるデータの構造

DigestInfo::= SEQUENCE {
  SEQUENCE {
    OBJECT IDENTIFIER / SHA1(1,3,14,3,2,26)
    NULL
  }
  OCTET STRING digest
}

この構造でバイナリデータを作成します。ASN.1のことを多少知っとく必要があります。
説明すると大変なので、以下の説明を参照ください。

③COMPUTE DIGITAL SIGNATUREする

かなり端折ってしまいましたが、こうして生成されたDigestInfoをCOMPUTE DIGITAL SIGNATUREコマンドに指定してGETしたデータが電子署名です。

4.検証

署名は検証(Verify)するためにあります。検証してみましょう。

「とっても大事な文書.pdf」というファイルの署名を前述の手順で作成し、「とっても大事な文書の署名.sig」というファイルで保存します。
さらに、前述の手順で取り出した公開鍵を「Authentication_PublicKey.der」というファイルで保存します。

  • とっても大事な文書.pdf
  • とっても大事な文書の署名.sig
  • Authentication_PublicKey.der

これらの情報があれば、Verifyの準備は完了です。
Verifyにはマイナンバーカードは必要ありません。

OpenSSLでVerifyする

コマンド2発です。
1発目でDER⇒PEM変換、2発目でVERIFYです。

Verify
c:\work>openssl rsa -pubin -in Authentication_PublicKey.der -inform der -out Authentication_PublicKey.pem
writing RSA key

c:\work>openssl dgst -sha1 -verify Authentication_PublicKey.pem -signature とっても大事な文書.sig とっても大事な文書.pdf
Verified OK

C#のプログラムででVerifyする

RSACryptoServiceProviderクラスのVerifyHashメソッドです。
手順をここで書くとまた長くなるので以下のソースを参照ください。
GitHubのソース

public static bool VerifySignature(byte[] publicKeyDER,byte[] signature,string targetFile)
がエントリポイントです。

  • とっても大事な文書.pdf ⇒ targetFile
  • とっても大事な文書の署名.sig ⇒ signature
  • Authentication_PublicKey.der ⇒ publicKeyDER

として見てください。

参考
C#でRSA暗号を使って署名や暗号化する

おつかれさまでした

もうすこしつづく
#3

38
29
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?