IT pass HikiWiki - [Exp2008]公開鍵認証を使ったssh接続実習 Diff

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

[
((<"2008 スケジュール表・各回資料(07/04)"|[Exp2008]スケジュール表・各回資料#07-2F04>))
]

{{toc_here}}


= 公開鍵認証によるsshの利用

ここでは, ssh 接続を行うときにユーザ認証を公開鍵認証で行う方法をおさらいします.

以下では Debian の OpenSSH,
バージョン "OpenSSH_4.3p2 Debian-9etch2" を例に取りますが,
基本的にOpenSSHであれば特に違いはないでしょう.

== 事前準備

今回の実習では, みなさんがそれぞれの班で使っている情報実験機とは別の情報実験機に ssh でログインしてもらいます.
そのためにまず, 普段使っている情報実験機とは別の情報実験機にアカウントを作りましょう.

以下のように4台の情報実験機を二組に分けます.

* joho01, joho04
* joho02, joho03

この組分けに従って, 例えば joho01 のみなさんは joho04 にアカウントを作ってください.
アカウント作成は((<"[Exp2008]Unix系OSでのアカウント作成">))で以前にやりました. 以前の資料を参考にしてください.

なお, ((<[Exp2008]情報実験機に遠隔ログインするために>)) で情報実験機
の IP アドレスを変更している場合には, 同じ組の情報実験機同士で
それぞれグローバル IP 同士もしくはプライベート IP 同士にしてください.
(そうでないと, 相互にログインすることができません).


== パスワード認証と公開鍵認証

ssh でログインするときの認証方法として,
おもなものにパスワード認証と公開鍵認証があります.
以下ではこの 2 つについて説明します.

=== パスワード認証

パスワード認証は, ユーザがパスワードを知っているかどうかで認証
を行う方法です.

=== 公開鍵認証

公開鍵認証は, 公開鍵と呼ばれる不特定多数に公開してよい鍵と,
プライベート鍵と呼ばれる特定の人だけが知っている鍵を使った認証方法です
(プライベート鍵は秘密鍵と呼ばれることもあります).

公開鍵認証では, 公開鍵に対応するプライベート鍵を
持っているかどうかで認証します.
ssh ではプライベート鍵をさらにパスフレーズと呼ばれる文字列
(パスワードほど長さ制限がなく, 空白文字を許した文字列)
で暗号化します.

ssh はデフォルトではパスワード認証も利用できますが,
EPA サーバではセキュリティの観点からパスワード認証は無効にしており,
公開鍵認証のみでアクセスできます.
これによって,
昨今ふえている ssh のパスワード認証を狙った
ブルートフォースアタック
(パスワードのいろいろな組合せを機械的に次々と試す, という攻撃)
を防ぐことができます.

既にみなさんは EPA サーバに ssh でログインできると思いますが,
復習として, もういちど公開鍵とプライベート鍵を作って,
それらを使ってリモートログインしてみましょう.


== 公開鍵とプライベート鍵

公開鍵暗号では公開鍵とプライベート鍵のペアが必要です.
OpenSSH で使う公開鍵とプライベート鍵は,
RSA と DSA のうちどちらかのタイプです.
RSA と DSA はSSHで認証に使う暗号の形式のことです.
鍵の長さ (bit数で数え, bit 長(びっとちょう)とよぶ) は,
DSAでは 1024 bit が推奨されています.
また,  RSA ではデフォルトで 2048 bit です.

鍵は長いほど暗号強度が強い(暗号が破られにくい)のですが,
むやみに長くても暗号化と復号化に時間がかかるだけで
あまり意味はありません.

鍵ファイルは, デフォルトでは次の位置に格納されます.
RSA か DSA のどちらかの鍵ファイルの組があると思います.

#RT
  , RSA, DSA
プライベート鍵, ~/.ssh/id_rsa, ~/.ssh/id_dsa
公開鍵, ~/.ssh/id_rsa.pub, ~/.ssh/id_dsa.pub


== 鍵の種類とbit長の確認

これまでに生成した鍵が DSA で 1024 bit 未満の場合や,
RSA で 2048 bit 未満の場合は, それぞれ推奨された bit 長に
変えてみましょう.

鍵ファイルがどのような鍵を保存しているかは,
単にファイルを見ても分からないかもしれません.
次のコマンド

% ssh-keygen -e

を実行すれば分かります.
ssh-keygen コマンドの -e オプションは,
公開鍵ファイルのフォーマットを「SECSH公開鍵フォーマット」に変換して出力します.
これはOpenSSHで使われているものとは別のフォーマットで,
商用製品で使われています.
SECSH公開鍵フォーマットは公開鍵の bit 長, 鍵の形式などを
コメント部分に記録します.
これを利用して公開鍵ファイルがどのような鍵を格納しているかを調べましょう.

ssh-keygen -e を実行すると, 対話的に鍵ファイルのパスをたずねられます.
ここで入力するのはプライベート鍵ファイルのパスです.
以下で実行例を示します.

[testuser@joho99 ~/] > ssh-keygen -e
Enter file in which the key is (/home/testuser/.ssh/id_rsa):
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "2048-bit RSA, converted from OpenSSH by testuser@joho99"
AAAAB3NzaC1yc2EAAAABIwAAAQEAs4veyMoKWgE9DCu/Qb/QgvN5b2j26G712h+GbFywnZ
Q9EtEVnWluBLi263AdWtgLurHnDn+qXq6MrXaGVw0hS0mI2AICxfs1etRrB7SPfmaynfqH
HQyuZ90tY0v/5tIiClNwexDBgEvE5vpOKZmf/FsGccko+eLgTDXxo0r/WkWYQpIrHiqCmj
l32DTDID+cHZjYUmFPqpB61u+Qky7EmkQtdwzgDjWB59pNAB4jIgGDHNlPrdudgFNWn77E
XKxz9pi3kiA40Oz6PFRmU6CDGjMdkOz9J3QsQ2LPZYBLAFzCncW0ZGjg7OSJtHImCwJ7P6
uMu0A4Kime07ykaZcniw==
---- END SSH2 PUBLIC KEY ----

Comment: 行に注目してください. この鍵の場合は鍵の bit 長が 2048 の RSA 形式であることがわかります.

== 鍵の生成

では, 鍵を生成してみましょう.
今回はこれまで作った鍵とは別に, 実習用の鍵を作ります.
そのためには, 公開鍵とプライベート鍵をデフォルト以外のパスに
作る必要があります.
そうしないと, デフォルトのパスにある鍵が上書きされてしまうからです.

RSA 公開鍵で鍵の長さとして 2048 bit を指定するには,
次のように入力してください.

% ssh-keygen -t rsa -b 2048

ここでは二つの項目を対話的に入力します.
まずプライベート鍵ファイルのパスを入力します.
パスを入力せずにエンターを押すと, デフォルトのパスが選択されます.
以前の鍵ファイルが上書きされてしまうので注意して下さい.
コマンドラインオプション "-f key_path" で
プライベート鍵ファイルのパスを key_path に指定することもできます.

次にパスフレーズを入力してください.
このパスフレーズによって, プライベート鍵を 3DES という暗号で暗号化して守ります.
パスフレーズは空でも設定できますが,
その場合はファイルシステムのアクセス制御機構(パーミッション)
だけに頼ることになります.
これはとても勧められません.

こうして, プライベート鍵と公開鍵の対(つい)が生成されます.

ちなみに, 引数なしで ssh-keygen を実行した場合は,
デフォルトで rsa, 2048 bit の鍵を生成します.

実行例です.

[testuser@joho99 ~/] > ssh-keygen -t rsa -b 2048
Generating public/private rsa key pair.
Enter file in which to save the key (/home/testuser/.ssh/id_rsa): /home/testuser/.ssh/id_rsa_exp   ← プライベート鍵ファイルのパスを入力
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/testuser/.ssh/id_rsa_exp.
Your public key has been saved in /home/testuser/.ssh/id_rsa_exp.pub.
The key fingerprint is:
d3:84:02:07:31:5f:09:fd:59:11:cf:47:cd:a8:f3:b8 testuser@joho99


== 鍵の設置

いま生成した公開鍵とプライベート鍵の対を使って公開鍵認証を行うには,
公開鍵をリモートホストに転送し, 適切な場所に置く必要があります.

実習では, 情報実験機 01 と 04 どうし, 02 と 03 どうしで公開鍵認証を使ってログインしてみましょう.

今回は scp コマンドでパスワード認証を利用して公開鍵をリモートホストに転送することにします.

情報実験機 01 にログインした状態で,
RSA 公開鍵 ~/.ssh/id_rsa_exp.pub を情報実験機 04
に転送する例を示します. (情報実験機の設定によっては,
IP アドレスは異なるかもしれません).

joho01% scp ~/.ssh/id_rsa_exp.pub 192.168.16.58:

このときに, ホスト鍵に関する次のような注意が出力されるはずです.

The authenticity of host 'joho04 (192.168.16.58)' can't be established.
DSA key fingerprint is 8a:7a:8f:19:c6:a7:b6:cd:db:25:08:bc:20:41:3d:20.
Are you sure you want to continue connecting (yes/no)?

リモートホストの公開鍵のフィンガープリント(ハッシュ値)を出力し,
リモートホストの公開鍵を ~/.ssh/known_hosts に記録してよいか
尋ねられます.
ここでは yes を選択してください.
ホスト鍵については後ほど触れます.

次に, 転送した公開鍵を ~/.ssh/authorized_keys に追記します.

  joho01% ssh 192.168.16.58      ← ssh で情報実験機にログイン
  joho04% mkdir -p ~/.ssh        ← ~/.ssh を作成
  joho04% chmod 700 ~/.ssh       ← 自分だけしか見られないようにパーミッション設定
  joho04% cat id_rsa_exp.pub >> ~/.ssh/authorized_keys
  joho04% rm id_rsa_exp.pub
  joho04% exit

さらに複数の公開鍵を登録する場合には,

  joho04% cat id_rsa_exp2.pub >> ~/.ssh/authorized_keys

などとして ~/.ssh/authorized_keys のファイル末尾に追記してください.


== 公開鍵認証によるリモートログイン

さて, これで公開鍵認証によるリモートログインの準備が整いました.
次のように ssh 接続を行うときに, パスワードでなくパスフレーズを尋ねられ, ログインできることを確認してください.

このとき, -i オプションでプライベート鍵ファイルを指定します.
前節でリモートホストに転送して設置した公開鍵に対応したプライベート鍵ファイルを指定してください.

joho01% ssh -i ~/.ssh/id_rsa_exp 192.168.16.58

このプライベート鍵に対応した公開鍵がリモートホスト(192.168.16.58)に存在すると想定します.


= sshにおけるホスト認証

ここでは, ホストのなりすましを防ぐホスト認証について実験してみましょう.

これまではユーザを認証するためにユーザごとの鍵を取り扱ってきました.
ホストを認証するためにホストごとにも鍵があります.
それがホスト鍵です. ホスト鍵も公開鍵とプライベート鍵があります.
Debianでは
* /etc/ssh/ssh_host_dsa_key
* /etc/ssh/ssh_host_dsa_key.pub
* /etc/ssh/ssh_host_rsa_key
* /etc/ssh/ssh_host_rsa_key.pub
にホストの公開鍵と秘密鍵が dsa形式, rsa 形式で存在します.

ssh でリモートホストに初めて接続するとき,
ssh クライアントはリモートホストの公開鍵を ~/.ssh/known_hosts に登録します.
次回以降に接続するときには, ssh クライアントは以前に保存しておいた
リモートホストの公開鍵を ~/.ssh/known_hosts ファイルから探して,
公開鍵がいまアクセスしようとしているリモートホストのプライベート鍵と
対になっているかどうかを調べます.
もし対になっていることが確認された場合にはホストは最初に接続したときと
同じホストであるとみなします.
つまり認証成功です. その次にsshクライアントはユーザ認証の手続きに移ります.
もし対になっていなければ, 認証は失敗です.
ssh クライアントは警告メッセージを出力して (下記参照), 接続を切ります.

これが意味するのは,
* ホストがなりすまされている
または
* ホスト鍵が作りなおされた
のうちのどちらかです.

以下で, ホスト鍵(公開鍵とプライベート鍵)を作りなおして,
警告が出る様子を実際に見てみましょう.


ホスト鍵もユーザ鍵と同じもので, 単にファイル名と置いてある場所が違うだけです.
従って, ssh-keygenコマンドを使ってユーザ認証用の鍵と
同じように生成することができます.

((*ここまで進んだら, 相手の情報実験機の人達がここにたどり着くまで待ってください. *))

それでは, ホスト鍵を作りなおしてみましょう.
まず, sshd を停止します.

% sudo /etc/init.d/ssh stop

次に, ホスト鍵をバックアップします.

% cd /etc/ssh
% sudo mkdir backup
% sudo mv /etc/ssh/ssh_host* backup/

それから ssh-keygen を使って公開鍵とプライベート鍵を作ります.
ここでは DSA 形式の鍵を作ってください.  
パスフレーズは必ず空にしてください.

% sudo ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/root/.ssh/id_dsa): /etc/ssh/ssh_host_dsa_key
Enter passphrase (empty for no passphrase):  ←エンター
Enter same passphrase again:          ←エンター
Your identification has been saved in /etc/ssh/ssh_host_dsa_key.
Your public key has been saved in /etc/ssh/ssh_host_dsa_key.pub.
The key fingerprint is:
d7:64:ed:67:33:f3:00:fe:c0:14:4d:ba:a2:93:dc:28 root@joho99

これで DSA 形式のホスト認証用の公開鍵とプライベート鍵ができました.

そして sshd を起動します.

% sudo /etc/init.d/ssh start

((*ここまで進んだら, 相手の情報実験機の人達がここにたどり着くまで待ってください. *))

次に, ssh で相手の情報実験機にログインしてみましょう.

$ ssh -i ~/.ssh/id_rsa_exp 192.168.16.??
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the DSA host key has just been changed.
The fingerprint for the DSA key sent by the remote host is
ad:0f:0c:d6:73:6c:26:ae:c1:10:d7:f1:31:18:ec:69.
Please contact your system administrator.
Add correct host key in /home/testuser/.ssh/known_hosts to get rid of this message.
Offending key in /home/testuser/.ssh/known_hosts:2
DSA host key for joho99 has changed and you have requested strict checking.
Host key verification failed.

保存しているホスト鍵とリモートホストのホスト鍵が違う! と怒られました.
もしリモートホストが「なりすまされ」ている場合には,
ssh クライアントはこのように警告をしてくれます.

いまは, 鍵を作りなおしたために警告が出たので,
もう一度ログインできるようにしましょう.

それには, ~/.ssh/known_hosts に登録してあるホストの公開鍵を消す必要があります.
このファイルは一行に1つのホストの情報が書かれていて,
先頭にリモートホストのドメイン名かIPアドレスが書いてあります.
警告メッセージに何行目にあった公開鍵をホスト認証に使ったか
出ていますので, その行をエディタで消します.
上の例では
Offending key in /home/testuser/.ssh/known_hosts:2
と書いてある部分の最後に行数が書いてあります.
つまり2行目です.

消したら, もういちど ssh でリモートホストにログインを試みてください.
今度は, リモートホストの ~/.ssh/known_hostsに登録するかどうか尋ねられます.
yes と答えてください.

The authenticity of host 'joho99 (192.168.16.??)' can't be established.
DSA key fingerprint is ad:0f:0c:d6:73:6c:26:ae:c1:10:d7:f1:31:18:ec:69.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'joho99' (DSA) to the list of known hosts.

もし, ssh で最初にリモートホストにアクセスするときにリモートホストが
「なりすまされ」ていたら, この手順では「なりすまし」を防げません.
このような「なりすまし」をも防ぐには, リモートホストの公開鍵の
フィンガープリントを事前にリモートホストの管理者から教えてもらい,  
ssh でアクセスするときに出力される公開鍵のフィンガープリントと
一致するかどうか調べる必要があります.

終ったら, バックアップしたホスト鍵を元に戻しておきましょう.

% cd /etc/ssh
% sudo mv backup/* .
% sudo rmdir backup

今回は, ホスト認証用の鍵として DSA 形式のホスト鍵だけしか
作りなおしませんでしたが, RSA 形式のホスト鍵は, DSA 形式の
ホスト鍵が存在しない場合にだけ使われるようです.


= 認証エージェント

何度も長いパスフレーズを入力するのはとてもうんざりさせられるものです.
その長いパスフレーズの入力を最小限に済ませる方法があります.
それは認証エージェントを使うことです.

パスフレーズは, プライベート鍵の暗号化のために必要なものでした.
認証エージェントは, プライベート鍵を復号化した状態でメモリに保存します.
ssh が起動され, プライベート鍵が必要になったときに,
その保存しておいたメモリの内容を使います.

こうして, ssh を使うたびにプライベート鍵を復号化する....言い替えれば, パスフレーズを入力する必要がなくなります.

== 認証エージェントの使い方

使い方はこうです. 例えば,

  % ssh-agent bash

などと実行して, シェルを起動します. それから,

  % ssh-add ~/.ssh/id_rsa_exp

として, プライベート鍵を復号化して, メモリに読み込みます.
このときにパスフレーズの入力を求められます.
こうすると, 以降 ssh 接続を行う際にパスフレーズを入力せずに済みます.
ssh-add コマンドを引数なしで起動すると,
デフォルトのパスにあるプライベート鍵を読み込みます.

% ssh -i ~/.ssh/id_rsa_exp リモートホスト名

などと ssh 接続を行うときに, パスフレーズを尋ねられないことを確認してください.

== 認証エージェントを使う際の注意

認証エージェントはとても便利ですが, 認証エージェントを利用している間は
パスフレーズを入力しなくてもリモートホストにアクセスできてしまいますから,
決して他人が端末を触れる状態で席を外してはいけません.
席を外すときは xlock などのコマンドで, スクリーンをロックし,
パスワードを入力しないと操作できないようにしましょう.

また, メモリに読み込んだプライベート鍵をメモリから消去するのも一手です.
それには

  % ssh-add -D

とします.

:実習:認証エージェントを使って, sshやscpをその都度パスフレーズを入力することなく利用してみて下さい. また, ssh-add -Dを実行して, 再びsshやscpを使うときにパスフレーズの入力を求められることを確認して下さい.

== 認証エージェント おまけ

一部のウインドウマネージャではデフォルトで ssh-agent を起動するようになっているので, 単に ssh-add とすればOKです.

ssh-agent を起動している場合は, 環境変数 SSH_AUTH_SOCK, SSH_AGENT_PID が設定されています. シェルで既にこの環境変数が指定されている場合は, ssh-agent が起動していると思ってよいです(kill(1)コマンドなどで故意に終了させた場合は別です).

みなさんが使っている環境で ssh-agent が既に起動していて利用可能かどうか, 調べてみてください.

例えば,
% ssh-agent bash
のような使い方は, 親プロセスの環境変数が子プロセスに伝播することを利用しています. 詳しく理解するには「プロセス」について調べてみて下さい.


= パスワード認証

公開鍵認証のついでに, パスワード認証についても理解を深めてみましょう.

パスワード認証は, ユーザからパスワードを入力してもらい,
そのパスワードが以前に入力しておいたパスワードと同じであれば
正しいユーザと認めるという認証方法です.

== 一方向ハッシュ関数

そのパスワードはそのまま保存されているわけではありません.

ここで一方向ハッシュ関数というものが登場します.
一方向ハッシュ関数は, 入力されたデータからふつう元のデータより
短い文字列(hash, ハッシュ)を出力します.
その文字列から入力されたデータを推測することはたいへん難しい,
という性質を持ちます(逆関数が求められない).
従って, ハッシュを盗まれてもただちにパスワードが盗まれたことにはなりません.
(とはいえ, 昨今の計算機の能力ではパスワードをブルートフォースアタックで
調べるのは比較的簡単ですので, 盗まるのは十分危険です. )

Debian をはじめとする Linux や Sys V 系 Unix では /etc/shadow に
パスワードが(デフォルトでは) MD5 という一方向ハッシュ関数でハッシュにされて保存されています.

== パスワードの保存形態

その前に, パスワードをばらしてもよいものに passwd コマンドで一時的に変更してください. (あとで元に戻してください!)

以下では, パスワードを "hogehoge?" にしたとします.
(念のため書きますが, " は除きます)

どんなふうに保存されているか見てみましょう.

/etc/shadow の自分のユーザ名の行を探します.
以下では testuser というユーザを例にとります.

testuser:$1$6QKVTUez$1zOBDbRH4E.qpQyxSMjnG/:14060:0:99999:7:::

/etc/passwd, /etc/shadow といったアカウント情報の入ったファイルは
":" (コロン)区切りのフィールドに分かれているのでした.
/etc/shadow は第一フィールドがユーザ名(ここではtestuser),
第二フィールドにパスワードのハッシュとソルトが入っています.

第二フィールドを抜き出してみます.

$1$6QKVTUez$1zOBDbRH4E.qpQyxSMjnG/

このうち, $1$は ハッシュを求めるのに MD5 を使っていることを示しています.
$1$ と次の $ に挟まれた 6QKVTUez がソルトです.

ソルトの次の 1zOBDbRH4E.qpQyxSMjnG/ がハッシュです.

== ソルト

ソルトはランダムに決められた文字列で,
辞書攻撃に耐えられるようにするためにパスワードといっしょに使います.

「ソルト」がなかった場合を考えてみると, ソルトの意義がよくわかります.
もしソルトがなければ, あるパスワードを入力した時のハッシュが
決まってしまいます.
それならば, パスワードに対応したハッシュをあらかじめ全て計算しておいて,
ハッシュからパスワードを求めたくなったらその表を参照すればよいのです.
そうすると準備に時間はかかりますが, いざというときに一瞬で
パスワードが分かってしまいます.
それを防ぐには, 表の数を膨大な数に増やしてしまえばいいのです.
そのために「ソルト」という乱数を使って,
「ソルト」ごとに表が必要なように工夫しています.

== ハッシュの計算を実際にやってみよう

さて, これを実際にどうやって求めているかスクリプト言語の一種の Perl を
用いて調べてみましょう.

実行例:
[testuser@joho99 ~/] > perl -e 'print crypt("hogehoge?","\$1\$6QKVTUez")'
$1$6QKVTUez$1zOBDbRH4E.qpQyxSMjnG/[testuser@joho99 ~/] >

perl -e "なんとか" とすると, "なんとか"という文字列を
Perlで書かれたスクリプトとみなして実行します.
これはコマンドラインから簡単なプログラムを実行したいときに役立つオプションです.

crypt関数は第一引数にパスワード, 第二引数にソルトを与えると
ハッシュを計算します.
そのハッシュを print 関数で出力させています.

Perl言語では""で囲った文字列の中に $ 記号があると, その $ 記号は特別な意味を持ちます. ここでは \$ として $ 記号をエスケープすることで普通の文字列として扱わせています.

ちなみに, ruby を用いた場合は次のようになります.
[testuser@joho99 ~/] > ruby -e 'p "hogehoge?".crypt("$1$6QKVTUez")'
"$1$6QKVTUez$1zOBDbRH4E.qpQyxSMjnG/"


こうして, 「$1$ソルト$ハッシュ」という形式で /etc/shadow に書かれていた文字列と
同じ文字列が得られるを確認してください.

おまけ1:
ソルトを少し変えてみて, ソルトによってハッシュが全く異なることを確認してみましょう.
おまけ2:
パスワードを少し変えてみて, パスワードを少し変えただけでハッシュが全く異なることを確認してみましょう.


これで本日の実習はおしまいです.
お疲れさまでした.

[
((<"2008 スケジュール表・各回資料(07/04)"|[Exp2008]スケジュール表・各回資料#07-2F04>))
]