保管しているパスワードの暗号化にLet's Try! ～その1. どのように秘密鍵を暗号化するか？

# はじめに

以前仕事で、「リリース済みの製品に対してDBに格納されている情報を暗号化する」という要件に対応したことがあるのですが、中々楽しかったので記憶を掘り起こしつつ見える化しようと思います。
ちょうど先日宅ふぁいる便の平文パスワード流出が話題になりましたし。

# ミッション:製品内DBに平文で保存されていた秘密鍵を暗号化したい！

とある製品でサーバー証明書等の証明書管理をDBで行っていました。ただこのDB、**秘密鍵を平文でそのまま保存していた**んですよね。DB自体の暗号化もされておらず。
基本閉じたネットワーク内で使われる製品だったのでそもそも「HTTPS設定自体利用頻度がほぼない」という理由でそのままになっていたのですが、遂に対応することになりました。

ただ元々セキュリティ周りを担当していた知識豊富な方が動けないということで、自分が担当することに。ヒアリング相手もいる状態で一から仕様検討出来る楽しい仕事でした。
このタスクで考えなければいけないことは2つ。こちらについてどんなことをしたのか？を2回に分けて書いていきたいと思います。

1. **どのように秘密鍵を暗号化するか？の検討～実装**
2. **どのようにDBをアップデートするか？の検討～実装**

# その1. どのように秘密鍵を暗号化するか？

これは独自に暗号アルゴリズムを考えるという意味ではなく、**既存の暗号アルゴリズムを活用してどう暗号化するか？**という意味。
というのも、**安易な暗号アルゴリズムをただ使うだけだと、少しの試行回数で解かれてしまう**からです。

## 暗号化の仕方次第では、内容を予測して簡単に解読出来てしまう

### 簡単な暗号化データを用意

実例で考えてみます。例えば以下のような手番で簡単にファイルを暗号化してみました。

1. "OpenSSL 暗号化"でググってぱっと目に付いたコマンドを使って暗号化
2. その暗号化ファイルをMarkdownに貼り付けるためにbase64エンコードしたもの

結果がこちらになります。

```console
U2FsdGVkX1/FtUcK0gqcb4zba8eUjNX0NwogUahZgjH2UO20bSJ+niX0IhONLK3b
```

上記データを全力で復号したい！となった場合、皆さんどのようにするでしょう？(CTFに自信ニキならこの時点で解読出来そう)

### 復号してみる

まず勘のいい方なら上記データの時点でbase64エンコードされているのは察しがつくと思うので、base64デコードしてファイル出力くらいはするでしょう。
さて出力結果から何がわかるか。皆さんご存知でしょうか？OpenSSLコマンドで暗号化したデータって、必ず頭に'Salted__'という文字列が付くんですよね(理由は後で書きます)。

```console
$ hexdump -C test.txt.enc | head -n 1
00000000  53 61 6c 74 65 64 5f 5f  c5 b5 47 0a d2 0a 9c 6f  |Salted__..G....o|
```

というわけで「これはOpenSSLのコマンドで暗号化したらしい」ということも予想がつきます。

そしたら次はどうしましょう。OpenSSLで利用可能な暗号アルゴリズムについては`openssl enc -ciphers`で確認できます。
私の環境では全101種類。shellかなんかで回せば十分試行出来る数ですね。
後必要なのは暗号化で使ったpass情報。普通は秘密鍵を使うんですが、コマンド上はファイルでもパスワード入力でも可能。
今回は簡単な暗号化なので、[2018年版の「最悪のパスワード・ワースト100」](https://gigazine.net/news/20181214-splashdata-worst-password-2018/)でも第2位に上がっている定番脆弱パスワード"password"を利用しています。
というわけでこんな感じに解読が可能となっています。

```
$echo -n "U2FsdGVkX1/FtUcK0gqcb4zba8eUjNX0NwogUahZgjH2UO20bSJ+niX0IhONLK3b" | base64 -d | openssl enc -e -aes-256-cbc -d -pass pass:password
暗号化してね～
```

### というわけで、「暗号化しました、だから安全！」ってわけではないよ

「自分で暗号化したものを自分で解いただけじゃん！」と突っ込みを受けそうですが、ここで言いたかったのはこんな話です。

1. 出力結果に「どのように暗号化したか？」のヒントがあるかもしれない
2. 暗号アルゴリズムは有限。暗号化で利用する秘密鍵がばれてしまえばちょっとしたプログラムで解かれてしまう

なので、**「既存の暗号アルゴリズムを活用してどう暗号化するか？」**という話になるわけです。

## どうやって暗号化すればいいのか？

やり方は様々だと思いますので、当時仕様検討した際のポイントだけ記載しておきます。

1. **暗号化されたデータから暗号化方法を予想されないようにする**
2. **秘密鍵がばれないようにする**

### 暗号化されたデータから暗号化方法を予想されないようにする

具体的に言われたのは、**暗号化されたデータが毎回同じにならないようにする**、**出力から予測できることをなるべく無くす**。

こちらセキュリティ周りを担当されていた方曰く、暗号化の基本だそうです。
理由は「暗号アルゴリズムを分析するヒントに繋がる」から。
前者は暗号化の結果が毎回同じ成果物になると、"これを暗号化すると必ずこうなる"という対応関係が出来る点がヒントになるということ。後者は言わずもがなですね。

じゃあ実際どうするの？必要なのは**ちょっとした味付け**だけでいいはず。

#### Salt

暗号化で使うデータに対して、ある値をくっつけてあげて実際の出力データを変える方法です。パスワードソルトとかって言葉がありますよね。あんな感じ

前述した**「OpenSSLコマンドで暗号化されたデータは頭に'Salted__'が入る」のもSaltが理由**です。
OpenSSLでは、暗号化したデータが一意にならないように8byteの乱数をくっつけて暗号化します。この乱数がOpenSSLのSalt。
ただ、このSaltを復号時にも利用しないといけないので、暗号化したデータの先頭に"Salted__ + 8byteのSalt"をくっつけているというわけです。

**OpenSSLコマンド⇒うまい事SaltをくっつけてくれるのでOK**。
**そうでない場合⇒例えば"暗号化対象物が一意に持っているIDをpassにくっつける"みたいに味付け**があるといいのかな。

#### 出力に含まれる情報をなくす

これはどちらかというとOpenSSLコマンドみたいな既存のツールをそのまま利用する際の話ですかね。「独自に何もしていない、プレーンなOpenSSLのコマンドだ！」って分かるだけでも大分出来ることが絞られてしまうので。
秘密鍵がしっかりしていれば問題ないと思いますが、出力ファイルを独自ルールでちょっと加工して「OpenSSLコマンドだ！」と分からなくするだけでもでかいんじゃないかな。

## 秘密鍵がばれないようにする

前半はなんとなくお作法的な印象。こちらが一番大事なポイントだと思います。
世にある暗号アルゴリズムは所詮有限ですし、組み合わせたとしても大した数にはならない。攻撃者的には鍵さえ分かってしまえばこっちのもんって感じだと思うので。
なので秘密鍵をどうするかがポイントに。このじゃあ暗号化で使う秘密鍵をどうするの？という部分に、システムの事情を加味した一工夫入れると良さそうですね。

私が対応した時は、既存アルゴリズムで秘密鍵を作成するのは当然として、「どうせやるならファイルシステム覗かれて秘密鍵がばれるレベルだとまずいよね？」みたいな話からこんな感じになりました。

1. 秘密鍵本体はその場でssh-keygen辺りで生成し、コードに埋め込む
2. 全個体が同じ秘密鍵を利用することが無いように秘密鍵を加工
    3. DBにseedテーブルを追加。テーブル作成時に乱数を挿入
    4. 秘密鍵×上記乱数でなんやかんやして新たな秘密鍵を生成
3. 生成した秘密鍵とsaltを使って暗号/復号化を行う

コード埋め込み＋最悪の最悪でhexで覗かれて埋め込んだ秘密鍵がばれても即死しないように一工夫しました。

# その1の最後に

というわけで、当時対応した経験をつらつらと記載させていただきました。その際に学んだことは、**暗号化は魔法の言葉ではない**ということです。
暗号化されている=復号化出来るということなので、例に出したように秘密鍵がざるなら誰でも元に戻せますし、暗号アルゴリズム自体も日々脆弱性が見つかって古くなっていきます。
**既存の仕組みで暗号化すればハイ安全！ではなく、どうやって使うかが大事**なので、暗号化の際はこの辺りに味付けしてあげると面白いんじゃないでしょうか。