Default to ― 2007/02/06 16:48
「(何かのパラメタの)初期値は 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 (公開鍵の片割れ) までもが記録されているからに過ぎない。
公開鍵の秘密 -追補- 2 ― 2007/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 を格納取り出している。
公開鍵の秘密 -追補- 3 ― 2007/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
購入した本
- 入門OpenSSH
新山 祐介(著)
秀和システム, ISBN-13: 978-4798013480, ASIN: 479801348X, 2,415円
- 実用SSH-第2版-セキュアシェル徹底活用ガイド
Daniel J. Barrett (著), Richard E. Silverman (著), Robert G. Byrnes (著)
オライリー・ジャパン, ISBN-13: 978-4873112879, ASIN: 4873112877, 5,040円
SSH に関する本をふたつ読んだけれど,フツーに使うなら入門OpenSSHで十分。
購入した DVD
- Emergency First Response
PADIのEFRの教材。ゆえに一般には手に入りません。2月末に熱海で講習をすることになったので購入。ビデオ版を既に持っているけれど,持ち運びや頭だしや劣化のことを考えると DVD のほうが便利で安心。
最近のコメント