Default to2007/02/06 16:48

最近、英文メールでよく見かける表現に default to というのがある。

「(何かのパラメタの)初期値は hoge に設定されている」という意味で、It defaults to hoge. という感じで使われている。The default value of this parameter is hoge. とか、It is set to hoge by default. と同じような意味らしい。

ところで、手元にある Longman Dictionary of Contemporary English にはこの用例がない。動詞としての default は、(金を払わなければならないという文脈で)するべきことをしなかった、という用例があるだけ。金融機関の債務不履行の「デフォールト」がこれにあたるのかな。

辞書が古いのか。default value (省略時設定)から転じて(他)動詞となった比較的あたらしい用法なのか...

公開鍵の秘密2007/02/06 17:52

わけあって SSH と戯れている今日このごろ。

基本から勉強しようと思い、新山祐介著『入門 OpenSSH』を Amazon で購入。文章は読みやすく説明も丁寧なので,すらすらと2時間ほどで読破したわけだが...

この本の P.41 の脚注に気になる記述がある。ユーザーが公開鍵認証でログインする仕組みを説明している本文についている脚注なのだが、全文引用すると、

厳密には、クライアントはまず自分の持っている秘密鍵に対応する公開鍵をサーバに送ります。その公開鍵がサーバに登録されている場合、サーバはある秘密の値をその鍵によって暗号化してクライアントに送ります。公開鍵によって暗号化された情報を正しく復号するにはそれに対応する秘密鍵が必要なため、もしクライアントがその値を正しく復号して送り返してくれば、そのクライアントは対応する秘密鍵をもっていると確信できます。

とある。さて問題となるのは冒頭の『厳密には、クライアントはまず自分の持っている秘密鍵に対応する公開鍵をサーバに送ります。』の部分だ。

TeraTerm Pro でも PuTTY でもなんでもいいのだが、SSH クライアントの導入手順をネットで調べてみるとすぐに疑問にぶつかる。どの説明でも、クライアントに公開鍵 (id_rsa.pub もしくは id_dsa.pub)をコピーする手順がないのだ。ほとんどの説明で、秘密鍵(id_rsa もしくは id_dsa)のみをクライアントにコピーしている。

『入門 OpenSSH』の記述間違いなのか。それにしては、サーバ側の ~/.ssh/authorized_keys に複数の公開鍵が登録されていても、sshd はクライアントが保持している秘密鍵に対応する公開鍵を迷うことなく見つけ出している。sshd のログをみながら確認してみたが、ユーザーの正しい公開鍵を見つけだすのに苦労しているそぶりはない。

クライアントはどうやって公開鍵をサーバへ送っているのだろう。秘密鍵から公開鍵が生成できるはずもなく...

sshd はどうやって、クライアントが保持している秘密鍵に対応する公開鍵をみつけるの?

だれかおしえて。

公開鍵の秘密 -2-2007/02/06 23:51

つづき。

RFC4252- The Secure Shell (SSH) Authentication Protocolを読んでみた。

7.  Public Key Authentication Method: "publickey"
-- snip --
To perform actual authentication, the client MAY then send a
signature generated using the private key.  The client MAY send the
signature directly without first verifying whether the key is
acceptable.  The signature is sent using the following packet:

      byte      SSH_MSG_USERAUTH_REQUEST
      string    user name
      string    service name
      string    "publickey"
      boolean   TRUE
      string    public key algorithm name
      string    public key to be used for authentication
      string    signature

と書いてある。読む限り,やはり Public Key Authentication では public key を実際にクライアントから送っても『よい(MAY)』送ることになっている。

『よい』というのは,必須(MUST)ではない。よく読むと、MAY なのは、この前段で boolean に FALSE を設定したパケットを送って、サーバが公開鍵認証を受け付けるかどうかを調べる手間を省いて、いきなり上記のパケットを投げつけても『良い』ということだった。

ここから先は実装依存ということか。OpenSSH のソースを見る必要があるのか... くぅ。

公開鍵の秘密 -3-2007/02/08 17:58

... さらにつづく。

早速 OpenSSH 4.5p1 のソースを落としてきて眺めてみた。

sshconnect2.c の userauth_pubkey() 関数あたりが、実際に公開鍵認証を実行しているクライアント側のコードのようである。

userauth_pubkey() の中盤あたりはこうなっている。

   1121                 if (id->key && id->key->type != KEY_RSA1) {
   1122                         debug("Offering public key: %s", id->filename);
   1123                         sent = send_pubkey_test(authctxt, id);
   1124                 } else if (id->key == NULL) {
   1125                         debug("Trying private key: %s", id->filename);
   1126                         id->key = load_identity_file(id->filename);
   1127                         if (id->key != NULL) {
   1128                                 id->isprivate = 1;
   1129                                 sent = sign_and_send_pubkey(authctxt, id);
   1130                                 key_free(id->key);
   1131                                 id->key = NULL;
   1132                         }
   1133                 }

つまり、ここまでの流れの中で id->key に値が設定されていないと、公開鍵が見つからなかったと判断して、load_identity_file()関数を呼んで秘密鍵を得る。(1126行目)。引数の id->filename は ssh コマンドを実行したときに -i オプションで渡したファイル名がセットされている。

id->key に秘密鍵を読み込んだところで、sign_and_send_pubkey() を呼び出す。この関数は同じ sshconnect2.c に定義されていて、サーバーへの接続はこの関数の中で行われる。sign_and_send_pubkey() では、

    869         if (key_to_blob(id->key, &blob, &bloblen) == 0) {
    870                 /* we cannot handle this key */
    871                 debug3("sign_and_send_pubkey: cannot handle key");
    872                 return 0;
    873         }

と、869行目で key_to_blob() 関数に読み込んだ秘密鍵を渡している。

key_to_blob() 関数は id->key に格納されている秘密鍵から "blob"へと変換する。blob とは「ひとかたまり」を意味する英単語で、データベースの Binary Large Object のことではない。

その key_to_blob() は key.c で定義されている。実際に秘密鍵から blob に変換する部分のコードは次の通りだ。

   757         switch (key->type) {
    758         case KEY_DSA:
    759                 buffer_put_cstring(&b, key_ssh_name(key));
    760                 buffer_put_bignum2(&b, key->dsa->p);
    761                 buffer_put_bignum2(&b, key->dsa->q);
    762                 buffer_put_bignum2(&b, key->dsa->g);
    763                 buffer_put_bignum2(&b, key->dsa->pub_key);
    764                 break;
    765         case KEY_RSA:
    766                 buffer_put_cstring(&b, key_ssh_name(key));
    767                 buffer_put_bignum2(&b, key->rsa->e);
    768                 buffer_put_bignum2(&b, key->rsa->n);
    769                 break;

ここで問題となるのが 767行目と 768行目の key->rsa->e と key->rsa->n だ。RSA とはつまりは秘密鍵と公開鍵の二つの非対称鍵を使うしくみなのだが、rsa->e は encryption exponent (暗号化指数)、rsa->n は RSA moduli (OpenSSH では public moduli: 係数) を意味する。rsa->e と rsa->n の組み合わせが公開鍵となり、秘密鍵は rsa->d (decryption exponent: 復号化指数) と rsa->n の組み合わせになる。

上記の流れで行くと、userauth_pubkey() で秘密鍵を id->key にロードし、さらに key_to_blob() で id->key にセットされている秘密鍵から暗号化指数 rsa->e と係数 rsa->n を読み出している。

さて。困った。

秘密鍵がもっているのは、復号化指数 (rsa->d)と係数(rsa->n)ではないのか?

userauth_pubkey() で秘密鍵を読み出しておきながら、つづく key_to_blob() でその秘密鍵から暗号化指数 (rsa->e)を読み出せているリクツはなんだろう。

load_identity_file() で id->key になにをロードしているのか調べる必要がある。

まだつづく... が...

どうも、RSA の基本からやりなおさなければだめなようだ...

公開鍵の秘密 -4-2007/02/08 23:07

転生編。

普段職場で RTM とというと,それは Release To Manifacturer である。製品の CD / DVD をプレスする工場へゴールデンマスター(製品のマスターコピー)を送る作業のことをいう。転じて,製品の開発が終了した状態を指すようになった。「次期バージョンの RTM が二ヶ月遅れる」というのは,開発期間が伸びて市場に投入する時期が二ヶ月遅れるという悪い知らせだ。

もちろん,今日の RTM はもうひとつの RTM: Read The Manualである。

そもそもの疑問は,「秘密鍵から公開鍵は計算できないはず」というものであった。であるのに,SSH-AUTH の段階でクライアントは平然と id_rsa (秘密鍵を納めたファイル)から公開鍵を生成しているんである。

OpenSSH のソースをあちこち這いずり回ったあげく,load-identity-file() 関数のその先は,openssl の rsa.c を見ないと答えがわからないことがわかった。でもって openssl のソースはいま手元にない。

ところで,ひょんなことから ssh-keygen(1)のマニュアルを読んでいたら,次のオプションがあることに気がついた。

     -y      This option will read a private OpenSSH format file and print 
              an OpenSSH public key to stdout.

ここにははっきりと秘密鍵を納めたファイルから公開鍵を生成できることが書いてある。

ためしてみると,

$ ssh-keygen -y
Enter file in which the key is (/Users/ats/.ssh/id_rsa): 
Enter passphrase: 
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAuSlWJTJpZ8pKPCbOyYyVhmQaTSvmfz1r8uZ5cnakts8K
x1Vgsruu9bfSqC7CF2zVs2QNSOo8BotkjVBEg3npf8RaPRit4i/f2MYihveUustg9HT56bD4ZMQq/+0W
SZpYedzL2AMTeqzGgj2Z6CoY3ypMOEmvMpArTcwAapv9esxUpeikjggcY+99c50gCGjf0Gb6MJT8eSqA
yL5oh5chcYOS1PevCihoo5qoESr7ZAdVvzplsjsRwmKhHDjpo6jxGpqqGaf/5jz2OE78tUV2uhd2FHts
NtZw7ewRiDLSdEO1z2iPYXyXyu+M+ZwuzY37u6ZoFkuKCCKs3/l+db6Isw==

$ cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAuSlWJTJpZ8pKPCbOyYyVhmQaTSvmfz1r8uZ5cnakts8K
x1Vgsruu9bfSqC7CF2zVs2QNSOo8BotkjVBEg3npf8RaPRit4i/f2MYihveUustg9HT56bD4ZMQq/+0W
SZpYedzL2AMTeqzGgj2Z6CoY3ypMOEmvMpArTcwAapv9esxUpeikjggcY+99c50gCGjf0Gb6MJT8eSqA
yL5oh5chcYOS1PevCihoo5qoESr7ZAdVvzplsjsRwmKhHDjpo6jxGpqqGaf/5jz2OE78tUV2uhd2FHts
NtZw7ewRiDLSdEO1z2iPYXyXyu+M+ZwuzY37u6ZoFkuKCCKs3/l+db6Isw== ats@mika.local

まさしく,id_rsa から id_rsa.pub と同一の内容を生成している。

... となると,秘密は id_rsa のファイル形式にある。

まだまだ続く。

公開鍵の秘密 -5- 完結編2007/02/09 00:19

完結編。

やはり,まずは標準にあたるべきである。

RFC 2313 - PKCS #1: RSA Encryption Version 1.5のセクション 7.2 にこうある。

7.2 Private-key syntax

   An RSA private key shall have ASN.1 type RSAPrivateKey:

   RSAPrivateKey ::= SEQUENCE {
     version Version,
     modulus INTEGER, -- n
     publicExponent INTEGER, -- e
     privateExponent INTEGER, -- d
     prime1 INTEGER, -- p
     prime2 INTEGER, -- q
     exponent1 INTEGER, -- d mod (p-1)
     exponent2 INTEGER, -- d mod (q-1)
     coefficient INTEGER -- (inverse of q) mod p }

   Version ::= INTEGER

   The fields of type RSAPrivateKey have the following meanings:
        o    version is the version number, for compatibility
             with future revisions of this document. It shall
             be 0 for this version of the document.
        o    modulus is the modulus n.
        o    publicExponent is the public exponent e.
        o    privateExponent is the private exponent d.
        o    prime1 is the prime factor p of n.
        o    prime2 is the prime factor q of n.
        o    exponent1 is d mod (p-1).
        o    exponent2 is d mod (q-1).
        o    coefficient is the Chinese Remainder Theorem
             coefficient q-1 mod p.

つまり,秘密鍵には encryption exponent も decryption exponent も両方格納されている。ご丁寧に,直後の note には,

        2.   The presence of the public exponent e is intended
             to make it straightforward to derive a public key from the
             private key.

ここで public exponent といっているのは,encryption exponent と同義だ。つまりは,public exponent(公開指数,暗号化指数)がここにあるのは,秘密鍵から公開鍵を容易に生成できるようにするためであると明解に書かれている。

あー。すっきりした。

つまりは,秘密鍵から公開鍵を生成できないというのは,僕の単なる思い込みなのであった。

正確には decryption exponent (d) と moduli (n) の組み合わせ(これが厳密な意味での秘密鍵だ)からは,encryption exponent (e) と moduli (n) の組み合わせ -- 公開鍵 -- は推測できない。だが,秘密鍵を格納しているファイルには,公開鍵を生成するための encryption exponent (public exponent)が仕込まれていたんである。

ソースを追いかけること 3時間。無駄だったのか,楽しかったのか...

公開鍵の秘密 -追補-2007/02/09 01:26

つまりは,秘密鍵から公開鍵を生成できないというのは,僕の単なる思い込みなのであった。

... とひとつ前に書いているが,厳密に言うと思い込みではない。

暗号化/復号化のために使う鍵のペアは,次のように導きだす。

まず,大きな素数をふたつ用意する。p と q とする。p と q を掛けて n を導く。n = p*q である。つぎに p と q からそれぞれ 1 を引いた数を求める。(p - 1) と (q - 1)である。この二つをかける。(p - 1)*(q - 1)。3よりも大きな数字で,この (p - 1)*(q - 1) と 1以外の公約数を持たない(つまり,たがいに素である)数を求める。これが e となる。つぎが複雑だ。「ある数に e をかけ(p - 1)*(q - 1)で割ると,あまりが 1になる」数を探す。その数が d となる。

暗号化と復号化に使うペアは,(d, n)と(e, n)だ。n を導きだした p と q は登場しない。n は十分大きな数字なので n から p と q を求めることはむずかしく,e と d はそれぞれ p と q から直接的に導きだされるから,p と q を知らなければ,e から d を導くことは難しく,また d から e を導くこともきわめて難しい。

つまり,厳密な意味では秘密鍵(d, n)から公開鍵(e, n)を求めることは計算上非常に難しく,逆もまたしかり,である。

秘密鍵から公開鍵を生成できるというのは,秘密鍵を納めた秘密鍵ファイル(OpenSSH では id_rsa)に厳密な意味での秘密鍵(d, n)以外に,計算の元となった p と q,そして (p - 1) と (q - 1),くわえて e (公開鍵の片割れ) までもが記録されているからに過ぎない。

RFC 2313 - PKCS #1: RSA Encryption Version 1.5に詳しい。

公開鍵の秘密 -追補- 22007/02/09 13:52

さて、昨日までの勉強で秘密鍵ファイルには、秘密鍵のほかにも公開鍵や、それらを生成する材料となったふたつの素数までもが格納されているらしいことがわかった。

実際に OpenSSH の ssh-keygen のコードを見てみよう。下は OpenSSH 4.5-p1 に含まれる ssh-keygen.c からの抜粋だ。do_convert_private_ssh2_from_blob() という関数が実際に -y オプションを与えられたときに RSA の秘密鍵の処理を行っている部分のようだ。 訂正: この関数は、秘密鍵を SSH2 形式へ変更する -X オプションを指定したときに呼ばれる関数である。 この関数は、一連のデータのかたまり(鍵の元となる)とその長さを受け取り、Key 型へのポインタを返す。この関数は結構な長さがあるので該当部分だけ抜き出すと、次のようになる。

    278         switch (key->type) {
    279         case KEY_DSA:
    280                 buffer_get_bignum_bits(&b, key->dsa->p);
    281                 buffer_get_bignum_bits(&b, key->dsa->g);
    282                 buffer_get_bignum_bits(&b, key->dsa->q);
    283                 buffer_get_bignum_bits(&b, key->dsa->pub_key);
    284                 buffer_get_bignum_bits(&b, key->dsa->priv_key);
    285                 break;
    286         case KEY_RSA:
    287                 e  = buffer_get_char(&b);
    288                 debug("e %lx", e);
    289                 if (e < 30) {
    290                         e <<= 8;
    291                         e += buffer_get_char(&b);
    292                         debug("e %lx", e);
    293                         e <<= 8;
    294                         e += buffer_get_char(&b);
    295                         debug("e %lx", e);
    296                 }
    297                 if (!BN_set_word(key->rsa->e, e)) {
    298                         buffer_free(&b);
    299                         key_free(key);
    300                         return NULL;
    301                 }
    302                 buffer_get_bignum_bits(&b, key->rsa->d);
    303                 buffer_get_bignum_bits(&b, key->rsa->n);
    304                 buffer_get_bignum_bits(&b, key->rsa->iqmp);
    305                 buffer_get_bignum_bits(&b, key->rsa->q);
    306                 buffer_get_bignum_bits(&b, key->rsa->p);
    307                 rsa_generate_additional_parameters(key->rsa);
    308                 break;
    309         }

key->type が KEY_RSA の場合には、はやり p, q, n, d, e を格納取り出している。

公開鍵の秘密 -追補- 32007/02/09 15:32

次に ssh-keygen が実際に秘密鍵を作成する部分のコードを見てみる。
ssh-keygen.c をたどっていくと、

   1338         private = key_generate(type, bits);
   1339         if (private == NULL) {
   1340                 fprintf(stderr, "key_generate failed");
   1341                 exit(1);
   1342         }

となっている。private は Key 型へのポインタである。key_generate() は key.c で定義されている。

    594 Key *
    595 key_generate(int type, u_int bits)
    596 {
    597         Key *k = key_new(KEY_UNSPEC);
    598         switch (type) {
    599         case KEY_DSA:
    600                 k->dsa = dsa_generate_private_key(bits);
    601                 break;
    602         case KEY_RSA:
    603         case KEY_RSA1:
    604                 k->rsa = rsa_generate_private_key(bits);
    605                 break;
    606         default:
    607                 fatal("key_generate: unknown type %d", type);
    608         }
    609         k->type = type;
    610         return k;
    611 }

key_generate() はとても短い。key->type が KEY_RSA のときには、rsa_generate_private_key() を呼び出す。この関数は key.c で定義されている。

    569 static RSA *
    570 rsa_generate_private_key(u_int bits)
    571 {
    572         RSA *private;
    573
    574         private = RSA_generate_key(bits, 35, NULL, NULL);
    575         if (private == NULL)
    576                 fatal("rsa_generate_private_key: key generation failed.");
    577         return private;
    578 }

鍵が RSA 形式の時には、rsa_generate_private_key() の中で RSA_generate_key() を呼んでいる。紛らわしいが、大文字で始まるほうの RSA_generate_key() は、openssl のソースに含まれているので、これ以上 OpenSSH のソースコードを漁っても何も得られない。

openssl-0.9.8d を展開したあと、crypto ディレクトリに移動するとそこに rsa_depr.c というソースコードがある。RSA_generate_key() はそこに定義されている。

     71 RSA *RSA_generate_key(int bits, unsigned long e_value,
     72              void (*callback)(int,int,void *), void *cb_arg)
     73         {
...中略
     92         if(RSA_generate_key_ex(rsa, bits, e, &cb)) {
     93                 BN_free(e);
     94                 return rsa;
     95         }

とまぁ、RSA_generate_key() のなかでは今度は RSA_generate_key_ex() を呼び出している。_ex つきの関数は rsa_gen.c の定義されている。これで何度目のリダイレクトだ?

rsa_gen.c では、

    78 int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e_value, BN_GENCB *cb)
     79         {
     80         if(rsa->meth->rsa_keygen)
     81                 return rsa->meth->rsa_keygen(rsa, bits, e_value, cb);
     82         return rsa_builtin_keygen(rsa, bits, e_value, cb);
     83         }

RSA_generate_key_ex() の関数は、これがすべてである。

本当の鍵の生成は rsa_builtin_keygen() を呼び出しておこなっており、rsa_builtin_keygen() は RSA_generate_key() の直後から定義されている。この関数は長いので割愛。

さて、話を元に戻すと、key_generate() の結果を private (Key 型へのポインタ)に保存すると、その先の処理は大きく分けて二つある。一つは private から公開鍵を生成すること、そしてもうひとつは private を秘密鍵として保存することである。

もう一度 key_generate() の部分を ssh-keygen.c から抜き出すと下記のとおりで、これは今回の冒頭に挙げているものと同じだ。

   1338         private = key_generate(type, bits);
   1339         if (private == NULL) {
   1340                 fprintf(stderr, "key_generate failed");
   1341                 exit(1);
   1342         }

private = key_generate(type, bits); の処理で、private には RSA の鍵情報が入っている。この直後に、

   1343         public  = key_from_private(private);

として、private の内容から公開鍵の情報を取り出して、やはり Key 型へのポインタである public へ代入している。

そして、最終的に、

   1405         if (!key_save_private(private, identity_file, passphrase1, comment)) {
   1406                 printf("Saving the key failed: %s.\n", identity_file);
   1407                 memset(passphrase1, 0, strlen(passphrase1));
   1408                 xfree(passphrase1);
   1409                 exit(1);
   1410         }

と、key_save_private() で identity_file に設定されているファイルへ書き出すのである。

最後に見るのは、key_save_private() だ。この関数は OpenSSH の authfile.c で定義されている。

    204         switch (key->type) {
    205         case KEY_DSA:
    206                 success = PEM_write_DSAPrivateKey(fp, key->dsa,
    207                     cipher, passphrase, len, NULL, NULL);
    208                 break;
    209         case KEY_RSA:
    210                 success = PEM_write_RSAPrivateKey(fp, key->rsa,
    211                     cipher, passphrase, len, NULL, NULL);
    212                 break;
    213         }

と、key->type が KEY_RSA なら PEM_write_RSAPrivateKey() を呼び出す。この PEM_write_RSAPrivateKey() はマクロで、openssl-0.9.8d/crypto/pem/pem.h に次のように定義されている。

    409 #define PEM_write_RSAPrivateKey(fp,x,enc,kstr,klen,cb,u) \
    410                 PEM_ASN1_write((int (*)())i2d_RSAPrivateKey,PEM_STRING_RSA,fp,\
    411                         (char *)x,enc,kstr,klen,cb,u)

つまり、PEM_ASN1_write である、と。

openssl-0.8.9d/crypto/pem/pem_lib.c に実際の PEM_ASN1_write() が定義されている。

結果から言うと、PEM_ASN1_write() からは更に PEM_ASN1_write_bio() を呼び出しており、正真正銘、これが最終最後の場所なのだが、はっきりいって、中身をみても良くわからん... 結局、ssh-keygen で id_rsa を生成するときに、p、q、d、n、e の情報を格納していると確信できる部分までだ取り付けていない。今後の宿題かなぁ...

暗号関連、嫌いです。

購入した本・CD・DVD -2007年2月2007/02/10 20:59

購入した本

SSH に関する本をふたつ読んだけれど,フツーに使うなら入門OpenSSHで十分。

購入した DVD

  • Emergency First Response

PADIEFRの教材。ゆえに一般には手に入りません。2月末に熱海で講習をすることになったので購入。ビデオ版を既に持っているけれど,持ち運びや頭だしや劣化のことを考えると DVD のほうが便利で安心。