[Exp2018]公開鍵認証を使ったssh接続実習

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

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

以下では Debian の OpenSSH, バージョン "OpenSSH_5.5p1 Debian-6+squeeze2" を例に取りますが, 基本的にOpenSSHであれば特に違いはないでしょう.

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

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

パスワード認証

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

公開鍵認証

公開鍵認証は, 公開鍵 (public key) と呼ばれる不特定多数に公開してよい鍵と, 秘密鍵 (private key) と呼ばれる特定の人だけが知っている鍵を使った認証方法です.

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

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

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

公開鍵と秘密鍵

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

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

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

RSADSA
秘密鍵~/.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 に指定することもできます.

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

こうして, 秘密鍵と公開鍵の対(つい)が生成されます.

ちなみに, 引数なしで 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

鍵の設置

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

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

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

情報実験機 01 にログインした状態で, RSA 公開鍵 ~/.ssh/id_rsa_exp.pub を情報実験機 02 に転送する例を示します. (IP アドレスはマシン上面に貼ってあります).

joho01-itpass:~$ scp ~/.ssh/id_rsa_exp.pub 10.35.19.202:

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

The authenticity of host 'joho02 (192.168.16.2)' can't be established.
RSA 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)?

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

ssh でリモートホストに初めて接続するとき, ssh クライアントはリモートホストの公開鍵を ~/.ssh/known_hosts に登録します. 次回以降に接続するときには, ssh クライアントは以前に保存しておいたリモートホストの公開鍵を ~/.ssh/known_hosts ファイルから探して, 公開鍵がいまアクセスしようとしているリモートホストの秘密鍵と対になっているかどうかを確認します(これがホスト認証です).

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

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

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

joho02-itpass:~$ cat id_rsa_exp2.pub >> ~/.ssh/authorized_keys

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

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

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

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

joho01-itpass:~$ ssh -i ~/.ssh/id_rsa_exp 192.168.16.2

この秘密鍵に対応した公開鍵がリモートホスト(192.168.16.2)に存在すると想定します.

○sshにおけるホスト認証

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

この項目では, 皆さんの普段使っている情報実験機とお隣の情報実験機との間で, 設定を変えながら相互に 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 を停止します. <-- ssh のポートを管理するデーモンです.

$ sudo /etc/init.d/ssh stop

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

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

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

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

これで RSA 形式のホスト認証用の公開鍵と秘密鍵ができました.

そして sshd を起動します.

$ sudo /etc/init.d/ssh start

ここまで進んだら, もう片方の情報実験機の人達がここにたどり着くまで待ってください.

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

$ ssh -i ~/.ssh/id_rsa_exp 192.168.16.XX
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    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 RSA host key has just been changed.
The fingerprint for the RSA 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
RSA host key for joho99 has changed and you have requested strict checking.
Host key verification failed.

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

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

それには, ~/.ssh/known_hosts に登録してあるリモートホストの公開鍵を消す必要があります.

公開鍵を消すためには, ssh-keygen を使います. 例えば, 192.168.16.XX の公開鍵を消すためには次のようにします.

$ ssh-keygen -R 192.168.16.XX

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

The authenticity of host 'joho99 (10.35.19.X)' can't be established.
RSA 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' (RSA) to the list of known hosts.

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

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

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

△認証エージェント

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

パスフレーズは, 秘密鍵の暗号化のために必要なものでした. 認証エージェントは, 秘密鍵を復号化した状態でメモリに保存します. 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

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

○パスワード認証

この実習を行う際は, レクチャー資料 Network Computing & Internet Security の補足資料 (Page54〜) も参考にして下さい.

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

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

一方向ハッシュ関数

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

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

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

パスワードの保存形態

では実際に, パスワードがどんなふうに保存されているか見てみましょう.

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

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

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

testuser:$6$4JH5O47n$ErolvWjqpb7Y6cCQmv/pXp/ohbZxjeEWe7ujCOBFPJpWbrQKX84JrETmXp4ezbU60GAe.mj.S4BQZUDD4jlDf.:15531:0:99999:7:::

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

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

$6$4JH5O47n$ErolvWjqpb7Y6cCQmv/pXp/ohbZxjeEWe7ujCOBFPJpWbrQKX84JrETmXp4ezbU60GAe.mj.S4BQZUDD4jlDf.:15531:0:99999:7:::

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

ソルトの次の ErolvWjqpb7Y6cCQmv/pXp/ohbZxjeEWe7ujCOBFPJpWbrQKX84JrETmXp4ezbU60GAe.mj.S4BQZUDD4jlDf. がハッシュです.

ソルト

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

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

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

これはコマンドラインから簡単なプログラムを実行したいときに役立つオプションです.

さて, これを実際にどうやって求めているか ruby を用いて調べてみましょう. ruby コマンドがインストールされていない場合は, apt-get を使ってインストールしてください.

実行例:

testuser@joho99:~$ ruby -e 'p "hogehoge?".crypt("$6$4JH5O47n")'
"$6$4JH5O47n$ErolvWjqpb7Y6cCQmv/pXp/ohbZxjeEWe7ujCOBFPJpWbrQKX84JrETmXp4ezbU60GAe.mj.S4BQZUDD4jlDf."

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

"パスワード".crypt("ソルト") とすると, ソルトを用いてパスワードのハッシュを計算します. そのハッシュを p メソッドで出力させています.

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

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

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

[2018 スケジュール表・各回資料(08/06)]