ソルト (暗号)

暗号理論におけるソルト（）とは、パスワードやパスフレーズなどのデータをハッシュ化する際に、一方向性関数の入力に加えるランダムなデータのことである。ソルトはパスワードをストレージに保存する際の保護に利用される。
歴史的にはパスワードが平文のままシステムに保存されていたこともあったが、時代を経るにつれ、パスワードに対する攻撃への保護策が開発されていった。ソルトはその一つである。

ソルトはパスワードごとにランダムに生成し、ソルトと平文のパスワードをパスワードハッシュ関数（PBKDF2、Argon2、bcrypt、scryptなど）に入力し、その出力をソルトとともにデータベースへ保存する（平文のパスワードは保存しない）。パスワードをハッシュ化することで、認証にあたって平文のパスワードを保存しておく必要がなくなる。これにより、保存していた認証情報が漏洩しても、平文のパスワードが漏れることはなくなる。

解読されにくくするために、パスワードハッシュ関数内部でハッシュ関数を繰り返し適用し、意図的に計算量を増やす手法をと言う。

ソルトを使うことで、辞書攻撃やレインボーテーブルを使った攻撃に対抗できる。ソルトの値は人間が記憶しておく必要はないため、ユーザーの負担を増やさずに、攻撃を成功させるために必要なレインボーテーブルのサイズを実現不可能なほど大きくできる。ソルトには毎回異なる値が使われるため、よく使われるパスワードを使っていた場合や、いろいろなサイトでパスワードを使い回していた場合でも、ソルトを加えたハッシュ値はそれぞれ別々になる。

暗号学的なソルトは、Unixの認証情報からインターネットセキュリティまで、現代的なコンピュータシステムで幅広く使われている。

ソルトはノンスの概念とも強く関連している。

 使用例  

ソルトを使ってパスワードを保存する手順の概要を、例をもとに示す。
次のテーブルは、ユーザー名とパスワードの組み合わせを2つ示している。ここではまだパスワードは保存されていない。

ユーザ名パスワードuser1password123user2password123

ソルトの値はランダムに生成する。NISTはPBKDF2の場合はソルトの長さは16バイト以上を必須としている
。この例では、ソルトの長さは8バイト（64ビット）とする。パスワードハッシュ関数に平文のパスワードとソルトを入力してハッシュ化する。出力はハッシュ値と呼ばれる。ソルトの値とハッシュ値をストレージへ保存する。以下の例の PBKDF2 は HMAC と SHA256 を使用し、イテレーション回数は600,000回を使用して計算した。

from hashlib import pbkdf2_hmac
print(pbkdf2_hmac('sha256', b'password123', bytes.fromhex('E1F53135E559C253'), 600_000).hex())

ユーザ名パスワード(保存しない)ソルトハッシュ値 = PBKDF2 (パスワード, ソルト)user1password123E1F53135E559C25308c8ce13b48b8b6b0a2114ded95e15d75d2a7a64de7486ae68e741e937b94d14user2password12384B03D034B409D4E4e44e7e134178b13f0e32dc3fb82ed6a1c0bf22a22f84f00d0830edce9ecbe5f

上のテーブルで示したように、ソルトの値が異なると、平文のパスワードが同じであっても、作られるハッシュ値はまったく別になる。
加えて、攻撃者がハッシュ値を事前に計算しておくことも難しくなるため、辞書攻撃の効果も低減できる。ただし、よく使われるパスワードや、簡単に推測できるパスワードに対しては、ソルトは効果がない。

 よくある誤用 

 ソルトの再利用 

プログラマがパスワードをハッシュ化する際に毎回同じソルトを使用するケース。
この場合でも、既存のレインボーテーブルを無効化することはできる（ソルトの値が適切であれば）。ただし、広く使われている製品にソルトがハードコーディングされると、そこから抽出したソルトを使って新しいレインボーテーブルを生成できてしまう。
また、ソルトが固定だと、パスワードが同じユーザーはハッシュ値も同じになってしまう（ハッシュ値を作る際にユーザ名を混ぜ込んでいる場合を除く）。これにより、一つのハッシュ値で複数のユーザーを攻撃するのが容易になってしまう。

 短すぎるソルト 

ソルトが短すぎると、ソルトが取りうる値すべてと、よくあるパスワードすべての組み合わせに対するレインボーテーブルを、攻撃者が容易に作成できてしまう。長いソルトを使うことで、レインボーテーブルのサイズを実現不可能なほど大きくすることができる。

 利点 

単一のパスワードに対する攻撃と、複数のパスワードに対する攻撃との違いを示すため、ユーザ名とハッシュ化したパスワードとの組が大量に記録されているファイルを考える。
ソルトがない場合、攻撃者はまずパスワードのハッシュ値hash(attempt[0])を一つ求め、その結果と一致するハッシュ値がファイル中にないか調べる。
一致するハッシュ値がある確率、つまりこの試行でパスワードを攻略できる確率は、ファイル中にあるパスワードが多いほど高くなる。
一方でソルトがある場合、攻撃者はまずファイル中のエントリAとソルト付きのハッシュ値hash(salt[a], attempt[0])を比較し、次にファイル中のエントリBとソルト付きのハッシュ値hash(salt[b], attempt[0])を比較し……という手順で攻撃を行うことになる。
以上の例より、ソルトを使うことで、複数のパスワードを攻略しようとした際にハッシュ値の「再利用」ができなくなることがわかる。

ソルトは、ハッシュテーブルやレインボーテーブルを使ったパスワードクラッキングの防止にもなる。ここでハッシュテーブルとは、よく使われるパスワードのハッシュ値を大量に集めたリストのことを指す。ソルトなしのパスワードの場合、攻撃者は各ユーザのエントリを見て、ハッシュテーブルやレインボーテーブルにハッシュ化されたパスワードと一致するものがあるか調べる。このテーブルルックアップがハッシュ関数の処理よりも充分に高速な場合（そうである場合がよくある）、この手法によりパスワードクラッキングを高速化できる。

一方、ソルトつきのパスワードの場合、ハッシュテーブルやレインボーテーブルに「ソルト + パスワード」をハッシュ化した値がなくてはならない。ソルトが充分に長くかつランダムであれば、そのような値があるケースは考えにくい。
人間が選んだソルトなしのパスワードは、記憶できる程度に短くかつ意味のある内容でなければならないので、辞書攻撃に対して脆弱になりがちである。
小さな辞書（またはそれをハッシュ化したハッシュテーブル）であっても、もっともよく使われるパスワードにクラッキングするには充分である。
ソルトは人間が記憶しておく必要がない。そのため、長いソルトを使えば、ユーザーに負担を強いることなく、攻撃を成功させるのに必要なレインボーテーブルのサイズを実現不可能なほど大きくすることができる。

より技術的には、ソルトはパスワードをより長く、より複雑にすることで、ハッシュテーブルやレインボーテーブルによる攻撃に対抗しているものと見なせる。
レインボーテーブル上に、ソルトつきのパスワードと長さ（たとえば8バイトのパスワードと2バイトのソルトで、レインボーテーブル上では10バイトのパスワードと同じ）や複雑性（ソルトが非英数字であれば、英数字のみのパスワードより複雑性が増す）の合うパスワードがなければ、レインボーテーブルからパスワードが見つかることはない。またパスワードが見つかった場合は、パスワードからソルト部分を取り除いて使用する必要がある。

シャドウパスワードを備えた現代的なシステムでは、パスワードのハッシュ値やその他のセキュリティに関するデータは非公開のファイルに格納されるため、これらのような懸念点は軽減されていると言える。
一方で、中央集約型のパスワード管理システムを使って複数のサーバを利用しており、パスワードやパスワードハッシュが複数のシステムへ送信されるような環境では、このような課題は引き続き妥当な懸念点であると言える。
そのような環境では、個々のシステムのrootアカウントより、中央集約型のパスワード管理システムの管理者の方が信頼されているという扱いになる。そのため、パスワードをハッシュ化する際のアルゴリズムが、ソルトの値の生成方法も含めて適切か確認することにも意味がある。

ソルトには、大量のパスワードを攻撃する辞書攻撃や総当たり攻撃の速度を低下させる効果もある（パスワード1つだけを攻撃する場合にはこの効果は望めない）。
ソルトがない場合、攻撃者が一度にたくさんのパスワードを攻撃するときには、推測したパスワードを1回ハッシュ化したら、その結果をすべてのハッシュ値と比較すれば済む。
一方でソルトがある場合、各パスワードのソルトはすべて異なると考えてよい。そのため、推測したパスワードは、攻撃対象のパスワードそれぞれのソルトを使って毎回ハッシュ化する必要がある。これにより、1つのハッシュ値をすべてパスワードと比較する場合と比べ、有意に速度が低下する。

ソルトのもう一つの（ただし小さな）利点として、2人のユーザが同じパスワードを使っていた場合、あるいは同じユーザが2つのマシンで同じパスワードを使っていた場合が挙げられる。
ソルトがない場合、このようなパスワードは同じハッシュ値としてパスワードファイルに保存される。
これにより、2つのアカウントが同じパスワードを使っていることが分かってしまうため、片方のアカウントのパスワードを知っている人は、もう一つのアカウントにもアクセスできてしまう。
一方で、パスワードにランダムなソルトを付与していた場合、2つのアカウントが同じパスワードを使っていたとしても、ハッシュを見ただけでそれが知られることはない。

 Unixにおける実装 

 1970～1980年代 

初期のUnixでは、パスワードファイル /etc/passwd にソルトつきのパスワードが保存されていた（パスワードの前に2文字のランダムなソルトが付いていた）。
この頃の古いバージョンのUnixでは、ソルトつきパスワードのハッシュ値といっしょに、ソルトの値も（平文で）passwdファイルに保存されていた。
またパスワードファイルはシステムのユーザは誰でも読むことができたが、これは、ユーザ権限で動くソフトウェアがユーザ名やその他の情報を読むためにこのような仕様となっていた。そのため、パスワードのセキュリティは一方向関数（暗号化、またはハッシュ化）だけで保護されている状態だった。
初期のUnixの実装では、パスワードの最大長は8文字までで、ソルトは12ビットであった。ソルトの取りうる値は4,096通りとなる。
これは1970年代における計算量とストレージのコストから見れば、妥当なバランスと言えた。

 1980年代 

ハッシュやソルトへのアクセスを制限するため、シャドウパスワードが導入された。ソルトは8文字、ハッシュ値は86文字となり、パスワードの最大長は無制限となった。

 Webアプリケーションにおける実装 

Webアプリケーションにおいては一般的に、ユーザのパスワードはハッシュ化されてデータベースに保存される。
この際にソルトがないと、SQLインジェクションが成功した場合に容易に攻略可能なパスワードができてしまう。
これは、複数のサイトでパスワードを使い回すユーザが多くいるためである。ソルトを使うことはWebアプリケーション全体のセキュリティを高めるために重要であると言える。各種プログラミング言語（PHP、.NETなど）でパスワードハッシュにソルトを使う方法については、外部リンクを参照。

 関連項目 
 パスワードハッシュ関数
 PBKDF2
 Argon2
 bcrypt
 scrypt
 パスワードクラック
 ノンス
 初期化ベクトル
 レインボーテーブル
 ペッパー (暗号)

 参考文献 

 外部リンク 

 OWASP Cryptographic Cheat Sheet
 how to encrypt user passwords

Category:暗号技術
Category:パスワード管理