ニフクラに登録したSSHキーと手元のキーが一致しているか確認する

これは、「[富士通クラウドテクノロジーズ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/fjct)」の10日目の記事です。

# はじめに

ニフクラには、作成されるサーバーに初期登録されるSSHの公開鍵をアップロードする機能があり、[コントロールパネル](https://pfs.nifcloud.com/help/ssh.htm) や [公式CLIツールを活用](https://qiita.com/ynott/items/e295b5f9217e9a8bd05d) して、手元で作った公開鍵をサーバーに登録することができます。
SSHキーはリージョン毎に管理されているため、最低でも利用するリージョン数、サーバーによって異なるキーを使う場合はその分だけ管理するキーが増えていき、不要になったカギを整理するうちに、「手元とニフクラコンパネのSSHキーが一致しない！このキーってなんだっけ？」ということになるわけです。（なるわけです）

ニフクラコンパネやAPI取得できる「フィンガープリント」の値を使って、ニフクラに登録されたSSHキーと手元のキーを確認することができるので、その方法をまとめておきたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49886/aa209cf8-f334-2005-1a5d-a4d65a2ab7df.png)

### 対象読者

`(ニフクラユーザー || ニフクラ開発者 ) && ( Linux を CUI で操作できる人 )`


# 罠

「SSH フィンガープリント」で検索すると出てくるであろう下記の方法では、
残念ながら「ニフクラのフィンガープリント」と一致する値は得られません。

```bash
ssh-keygen -lf <ssh の公開鍵 (e.g. path/to/id_rsa.pub )>
ssh-keygen -l -E md5 -f <ssh の公開鍵 (e.g. path/to/id_rsa.pub )>
```

# 「ニフクラのフィンガープリント」とは？


ニフクラのドキュメントを探すと、 [DescribeKeyPairs API のドキュメントページ](https://pfs.nifcloud.com/api/rest/DescribeKeyPairs.htm) に記載がありました。
読み解くと実は、「ニフクラフィンガープリント」とは、「DER形式のSSH公開鍵の情報の MD5 Hash値」の事を指すそうです。

DER とは、バイナリファイルのフォーマットの一種で、鍵や証明書をASN.1 というデータ構造で表現し、それをシリアライズしたものだそうです。[※出典](https://qiita.com/kunichiko/items/12cbccaadcbf41c72735)

鍵や証明書を保存したものとしては他にも「PEM」形式と呼ばれるものがあります。
PEM形式は、「.PEM/.pem」という拡張子に使われており、見覚えのある人も多いのではないでしょうか。
ニフクラ上でSSHキーを作成する機能を使うと、秘密鍵がこのPEM形式でダウンロードできます。

### ちなみに PEM とは？

鍵や証明書を保存したものとしては他にも「PEM」形式と呼ばれるものがありますが、こちらは、鍵や証明書を↑と同じように ASN.1 形式で表し、それを Base64 エンコーディングしたものとなります。
こんなの

```
-----BEGIN RSA PRIVATE KEY-----
6KyO44Gu5paH5a2X5YiX6KyO44Gu5paH5a2X5YiX6KyO44Gu5paH5a2X5YiX
772e5Lit55Wl772e6KyO44Gu5paH5a2X5YiX6KyO44Gu5paH5a2X5YiXCg==
-----END RSA PRIVATE KEY-----
```

実際にはもっと長いです。最初と最後の行が特徴的。

SSH公開鍵認証では秘密鍵を表現するのに利用されますし、 HTTPS 通信などで利用される SSL 証明書も PEM 形式で取り扱われることもあります。


# 実装

## Linux の Shell コマンドで確認する

### 秘密鍵から「ニフクラのフィンガープリント」を確認する

[DescribeKeyPairs API のドキュメントページ](https://pfs.nifcloud.com/api/rest/DescribeKeyPairs.htm)　の記載を読み解くと、下記のコマンドで「ニフクラのフィンガープリント」が得られることがわかりました。

```bash
$ openssl rsa -in id_rsa -pubout -outform DER | openssl md5 -c
writing RSA key
(stdin)= 70:6c:67:f5:32:be:14:4b:c3:37:fd:bf:49:df:3c:18
```

コマンドの内容を確認すると、

1. OpenSSL コマンドでSSH秘密鍵を読み込み、公開鍵をDER形式で出力
2. パイプから受け取った標準出力のmd5値を出力

という処理になっていることがわかります。
パイプの後は、 `md5sum` コマンドを使っても問題ありません。

※実験用に鍵にはパスフレーズを設定していません。パスフレーズが設定されている場合は　[DescribeKeyPairs API のドキュメントページ](https://pfs.nifcloud.com/api/rest/DescribeKeyPairs.htm)　の通り、

```bash
openssl rsa -in <path/to/id_rsa> -pubout -outform DER -passin pass:<password> | openssl md5 -c
```

としてください。

SSHの認証に利用する秘密鍵から公開鍵の「ニフクラのフィンガープリント」を取り出していますから、たいていの場合は↑で事足りますね。

### 公開鍵から「ニフクラのフィンガープリント」を確認する

一応、公開鍵から「ニフクラのフィンガープリント」を計算する方法も併記しておきます。
秘密鍵には公開鍵の情報が含まれているため、遊び程度の意味しかありませんが...

SSHで利用される公開鍵には様々な形式があるかと思いますが、 `ssh-keygen` で生成される `.pub` 形式の公開鍵は OpenSSH 独自のフォーマットとなっています。

こういうやつ

```
ssh-rsa AAAAB~~~~~ root@ubuntu
```

このままでは openssl コマンドでは読み込めないため、形式を変換して利用します。

```
ssh-keygen -f id_rsa_pass.pub -e -m PKCS8 | openssl rsa -pubin -pubout -outform DER | openssl md5 -c
```

用途があるかは不明ですが、公開鍵からも「ニフクラのフィンガープリント」を確認することができます。

## Golang のプログラムで確認する

自分が最近 Golang を使ったプロダクトの開発に携わっているので、ちょっと調べてみました。

### 秘密鍵から「ニフクラのフィンガープリント」を確認

```golang
package main

import (
        "crypto/md5"
        "crypto/rsa"
        "crypto/x509"
        "fmt"
        "io/ioutil"
        "os"

        "golang.org/x/crypto/ssh"
)

func main() {
        filePath := "./id_rsa"
        f, err := os.Open(filePath)
        if err != nil {
                fmt.Println(err)
                return
        }
        defer f.Close()

        // PEM 形式の秘密鍵を読み込む
        privateKeyPEM, err := ioutil.ReadAll(f)
        if err != nil {
                fmt.Println(err)
                return
        }

        // PEM 形式から interface{} 型として秘密鍵を取り出す
        privateKey, err := ssh.ParseRawPrivateKey(privateKeyPEM)
        if err != nil {
                fmt.Println(err)
                return
        }

        // interface{} 型から rsa.PrivateKey 型にキャスト
        rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
        if !ok {
                fmt.Println("error")
                return
        }
        err = rsaPrivateKey.Validate()
        if err != nil {
                fmt.Println("error")
                return
        }

        // DER 形式で公開鍵を取り出す
        publicKeyDer, _ := x509.MarshalPKIXPublicKey(&rsaPrivateKey.PublicKey)

        // DER 形式の公開鍵のMD5ハッシュ値を計算する
        publicKeyDerMd5Sum := md5.Sum(publicKeyDer)

        fmt.Printf("%x\n", publicKeyDerMd5Sum)
}
```

openssl コマンドでの手順と同様に、

1. SSH秘密鍵を読み込む
2. DER形式で公開鍵を取り出す
3. MD5ハッシュ値をとる

という手順で値を取り出します。

# おわりに

「ニフクラのフィンガープリント」と手元の秘密鍵が一致しているかを調べようとして、「SSH フィンガープリント」と検索して「罠」に嵌ってしまったところからこの記事につながりました。

MD5などのハッシュ関数を利用した、データをユニークに特定する不可逆な値を「フィンガープリント」と呼ぶらしいのですが、SSHの文脈だとすでに使われており混乱してしまいましたが、おかげで SSH の公開鍵認証の仕組みを調べて理解を深めることができました。

同じように困っている方の一助となれば幸いです。


明日は、 @kenta_ojapi が 「[営業からエンジニアへ転向して思ひしことを徒然に綴ろうかな]()」という興味深い記事を書いてくれるみたいです。お楽しみに。