QA@IT

適切なAES暗号鍵の作成方法は

48164 PV

セキュリティ超初心者です。

ユーザーが入力したパスワードを元に、
ユーザーデータを256bitのAESで暗号化したいのですが、
暗号鍵はどのように作成するのが安全でしょうか?

データはユーザー環境で暗号化します。
暗号理論は全くわかりませんが、とりあえず思いついたのが、


パスワードをそのまま鍵にする。
鍵長が足りない部分はソルトを足す。
(パスワードが長すぎる場合は?)


パスワードにソルトを付けて256bitのハッシュ値を取る。
ストレッチしたものを鍵とする。
(SHA256はもう危ない?)


②で得たハッシュ値で、十分な長さの適当なデータを暗号化する。
出来た暗号データのハッシュ値を鍵とする。
(暗号化されたデータは別の暗号の鍵として使える?)
(最後のSHA256は危ない?)


②で得たハッシュ値で、256bitの適当なデータを暗号化する。
出来た暗号データを鍵とする。


パスワードが256bitになるまでソルトを追加、
256bitの適当なデータをAESで暗号化する。
出来た暗号データを鍵とする


パスワードにソルトを付けたものをBlowFishの鍵とする。
BlowFishで256bitの適当なデータを暗号化、
出来た暗号データを鍵とする


パスワードをシードにした乱数を作成、そのハッシュ値を鍵とする。
(メルセンヌ・ツイスタでも危ないと言う人もいる)

開発言語はJavaとC#です。

宜しくお願いします。

回答

ユーザーが入力したパスワードを元に、
ユーザーデータを256bitのAESで暗号化したいのですが、
暗号鍵はどのように作成するのが安全でしょうか?

特に重要なのはパスワードの選択だと思います。256ビットのAESといっても暗号鍵が256ビットの本物の乱数なら256ビットの強度ですが、パスワードから鍵導出関数で作った暗号鍵を使う場合はそもそも強度がずっと低いです(追記1)。できるだけ良いパスワードを使うことが安全性の向上につながると考えられます。

ですのでユーザーにパスワードを入力させるときに、プログラムで入力されたパスワードの品質をチェックし、悪いパスワード(長さや文字種の不足するパスワードや辞書攻撃に弱いパスワード)を受け入れないようにするとよいのではないかと思います。またランダムなパスワードに高いスコアをつけて、ランダムなものを選んでもらうようにすればよいのではないかと思います。

鍵導出関数はいろいろあるとは思いますが、どれがいいかは私もわからないので専門家の評価を見て決めて下さい。あとはソルトの選択やiterationの回数に気をつけるとかでしょうね。鍵導出関数によっては他のパラメータも気をつける必要があるでしょう。

それと暗号化とパスワードのハッシュ化は別の話なので一応分けて考えたほうがすっきりすると思います。データ(平文)を暗号化するとは、復号すれば元の平文に戻るということです。ハッシュ化は一方向で、元のデータには戻せないようになっています。パスワードを保存する場合は通常はハッシュ化です(追記2)。どっちの話かわからないと混乱しますので。

追記1
キーストレッチングを行なうので一見強度も保たれそうですが、そうも行かないのではと思います。例えばかりに元のパスワードが12文字のランダムなものだったとして(文字は[A-Za-z0-9-_]の中から選ばれたとする)、鍵導出関数に渡す元の鍵長は実質72ビット相当と考えられます。

これを256ビットに引き延ばしても総当たりに要する最大試行回数は2^72と考えられるので(攻撃者も鍵導出できるなら2^72回試せば必ず当たりが出るはず)、1回当たりの試行に要する計算コストを引き上げて総当たりに要するトータルコストを2^256まで引き上げたいところです。しかし2^722^256は非常に大きい違いなので、直感的に見てそこまで引き上げるのは結構厳しいものがあると思います(もっと低い強度しか得られないのではと疑います)。

あと辞書攻撃に対する耐性はキーストレッチングだけでは十分には得られないと思います。例えば数字4文字のパスワードやパスワード"password"を使われた日には、キーストレッチングでどんなに頑張ってもあまり効果はないでしょうから。

追記2
これは送信されてきたパスワードを照合する側(主にサーバー)の話です。パスワード送信側(主にクライアント)だとパスワードを暗号化して保存しておき、復号してから送信することはよくあると思います。

パスワードマネージャーなどもパスワードを暗号化して保存しているのではないかと思いますが、ふと気になって見たら有名どころではPBKDF2が使われているようです。ぱっと見の印象にすぎませんが、恐らくユーザーはマスターパスワードを1個覚えておき、実際の個々のパスワードはマスターパスワードからPBKDF2で導出した鍵で暗号化されているような雰囲気ですね。

参考
https://www.dashlane.com/ja/security
https://www.lastpass.com/

編集 履歴 (5)

Argon2を使ってみたので、分かった範囲で再び自己レスです。

オリジナルのGitHubはこちら
https://github.com/P-H-C/phc-winner-argon2
 

C言語の場合はargon2i_hash_raw()を呼ぶだけ。

int argon2i_hash_raw( // 成功したらARGON2_OKを返す
const uint32_t t_cost, // 反復回数
const uint32_t m_cost, // 使用するメモリ(m_cost * 1024 が使われる)
const uint32_t parallelism, // 同時に実行するスレッド数
const void *pwd, // パスワード
const size_t pwdlen, // パスワードの長さ
const void *salt, // ソルト(16byteあれば十分)
const size_t saltlen, // ソルトの長さ
void *hash, // 出力されるハッシュ
const size_t hashlen // ハッシュの長さ
)

void *hash 以外の全てのパラメータが生成されるハッシュに影響する。

size_t hashlen でハッシュの長さを指定できるので、
ブロック暗号の鍵として使う場合、パスワードから暗号鍵と初期ベクトルを同時に作れる
 

■ 推奨される引数の設定方法(t_cost, m_cost, parallelism)
実行環境に合わせて引数を変えながらテストを繰り返し、調整していく。

・ハッシュ算出に使える時間tを秒単位で決めておく
・parallelism : 実行環境で利用できる最大数

・m_cost : まず実行環境で利用可能な最大容量にする
・t_cost : まず1にする
これでテストする

・算出時間がt未満ならt_costを1つ増やして再度テストする
・t_cost=1でも算出時間がtを超える場合、m_costを減らして再度テストする

算出時間がt未満となる最大値が最適な引数となる

 

■ その他
x86・x64用と公式に書いてあったが、コンパイルできればarmでも同じハッシュが得られる
Androidで実装する場合、NDKで行う必要がある。
Argon2側のC言語ソースはそのままAndroid StudioのNDKで使えた。
負荷が非常に高いので、UIスレッドで実行すると「Fatal signal 11 (SIGSEGV)」になる。

編集 履歴 (0)

調べてわかった範囲での自己レスです。

■一般的な方法
弱い               強い
PBKDF2 < Bcrypt < Scrypt < Argon2

■PBKDF2
SHA256を繰り返すことでハッシュを得る
一般的にはこれを使ってたらしいですが、GPUの並列解析が一番簡単で一番弱い。
今からハッシュ部分を作るのであればこれはもう使わないほうが良い。

■Bcrypt
Blowfishで特定のデータの暗号化を繰り返すことでハッシュを得る
得られるハッシュにソルトや繰り返し回数も含まれるので、
ユーザーパスをDBに保存するにはこれが良いらしい。
ハッシュ値は固定長ではないので、AES鍵の為にはBcryptの結果を更にSHA256する?

■Scrypt
GPU解析に対抗するために作られたアルゴリズム。
大量のメモリにランダムにアクセスすることで、
GPUからDRAMへのアクセスを増やし、解析速度を低下させる。
LiteCoinという仮想通貨で使われている。
最近はGPUでCPUの数十倍の計算速度が得られるようになってしまったらしい。

■Argon2
最新のパスワード用ハッシュアルゴリズム。
GPUへの解析耐性が非常に強いらしい
2015年のパスワード用ハッシュ大会で優勝
新しすぎて情報があまりないが、巷ではこれが最強ではないかと呼ばれている感じがする。

 

*著者はセキュリティ初心者だから間違ってるかもしれません。
 
 

役に立ちそうな資料
■セキュリティの重要課題:ユーザーのパスワードを安全に保管する方法について
https://www.sophos.com/ja-jp/press-office/press-releases/2013/11/ns-serious-security-how-to-store-your-users-passwords-safely.aspx

■Password Hashing: PBKDF2, Scrypt, Bcrypt
https://medium.com/@mpreziuso/password-hashing-pbkdf2-scrypt-bcrypt-1ef4bb9c19b3#.527kf2eaa

■scryptがGPUに破られる時
https://blog.visvirial.com/articles/519

編集 履歴 (1)
ウォッチ

この質問への回答やコメントをメールでお知らせします。