パスワードを安全に保管することは、信頼できるエンジニアにとって必須である。プレーンテキスト形式のパスワードは極めて安全性に欠けるため、わざわざプレーンな形式で保存する必要はないでしょう。誰かがデータベースの閲覧権限を得るだけで、ユーザー全体が危険にさらされる可能性があります。
ということです。
パスワードは、安全で、かつ管理しやすい方法でデータベースに保存する必要があります。
データベースが危険にさらされることを常に想定し、データを入手する可能性のある人物に、少しでも悪用されないよう、必要なすべての予防措置を講じる必要があります。特に、ユーザーのログイン情報やその他の機密データを保存しているデータベースには、それが当てはまります。
さらに – 倫理的な行動の問題でもあります。もしユーザーがあなたのウェブサイトにサインアップしたら、そのパスワードの広告をそのまま見つけることができるでしょうか。パスワードは多くの場合、複数のウェブサイトで使用され、個人情報が含まれており、またユーザーが公にしたくない一面を露呈する可能性があります。あなたも悪意のある人物も、平文のパスワードを読み取ることはできないはずです。このため、ウェブサイトはパスワードを忘れたときにメールで通知することができません。パスワードをリセットする必要があるのです。
パスワードのハッシュ化は、ウェブマスターと悪意ある行為者の両方からパスワードを安全に保つ、安価で安全な標準手順です。
ログイン情報をあからさまに悪用されないようにするには、データベースに保存する前に、必ずパスワードをハッシュ化する必要があります。これが、データベースに保存されたパスワードの不正使用を防ぐための、最もシンプルで、かつ最も効果的な方法です。仮に、誰かがユーザーのログイン情報を入手したとしても、その情報は、人間には読めず、計算機でも解読が困難な形式であるため、どのような形であれ、利用することはできないのです。
このガイドでは、PythonでBCryptを使用してパスワードをハッシュ化する方法について説明します。ハッシュとは何か、ハッシュの比較方法、「ソルティング」の仕組み、ハッシュによってパスワードが安全になることまで説明します。
パスワードハッシュとは?
ハッシュとは、ハッシュ関数を用いてある文字列を別の文字列に変換することです(ハッシュとも呼ばれます)。入力文字列のサイズに関係なく、ハッシュはハッシュアルゴリズムであらかじめ定義された固定サイズになります。ハッシュの目的は、入力文字列と全く同じ形にはならず、入力文字列が変化するとハッシュも変化することである。
さらに、ハッシュ関数は入力を一方通行でハッシュする。ハッシュ化されたパスワードは元に戻せません。入力されたパスワードがデータベース内のパスワードと一致するかどうかを確認する唯一の方法は、入力されたパスワードもハッシュ化し、そのハッシュ値を比較することである。この方法では、実際のパスワードが何であるかを知らなくても、それがデータベースのものと一致するかどうかを確認することができます。
注:このガイドでは、入力文字列に基づいて固定サイズのハッシュを計算するために使用される数学関数に対して「ハッシュ関数」という用語を使用します(一般的なハッシュ関数には、SHA256、SHA1、MD5、CRC32、BCryptなどがあります)。ハッシュアルゴリズム」とは、使用するハッシュ関数だけでなく、ハッシュ化の過程で変更可能な多くのパラメータを含む、ハッシュ化のプロセス全体を指す。
あなたが"myPwd"
のようなものをハッシュアルゴリズムに入れるたびに、あなたは全く同じ出力を得ることができます。しかし、"myPwd"
を少しでも変更すると、出力は認識できないほど変わってしまう。
このため、同じような入力文字列でも全く異なるハッシュが生成される。もし似たようなパスワードが同じハッシュを生成するとしたら、ある単純なパスワードをクラックすることは、他の文字のルックアップテーブルを作成することにつながるかもしれない。一方、同じ入力からは常に同じ出力が得られるので、ハッシュはかなり予測可能である。
予測しやすいということは、悪用されやすいということです。
あるパスワードのハッシュ化に使用されたハッシュ関数が分かっている場合(そして、使用されているハッシュ関数のリストがそれほど多くない場合)、考えられるすべてのパスワードを推測し、それらを同じハッシュ関数でハッシュ化し、得られたハッシュと解読したいパスワードのハッシュを比較すれば、パスワードを解読することができます。この種の攻撃はブルートフォース攻撃と呼ばれ、かつてはpassword123
、12345678
などのような単純なパスワードに対して非常によく機能したものです。
ブルートフォース攻撃を防ぐ最も簡単な方法は、計算に比較的時間がかかるハッシュ関数を使用することです。そうすれば、ブルートフォース攻撃は可能な限りのハッシュを計算するのに非常に時間がかかるため、実行する価値さえないでしょう。
さらに、ほとんどのウェブアプリケーションには、不正なパスワードが一定回数入力されると「タイムアウト」する機能が組み込まれており、制御されたUIからパスワードをブルートフォースで推測しようとすると、ブルートフォース攻撃を実行不可能にしてしまいますが、誰かがハッシュ化したパスワードのローカルコピーを取得した場合はこの限りではありません。
パスワードハッキングにおけるSaltとは?
暗号技術、計算単価、技術の進歩に伴い、データベースに保存されたパスワードを保護するためには、適切なハッシュ関数を選択するだけでは十分ではありません。場合によっては、優れたハッシュ関数であっても攻撃を防ぐことができないこともある。したがって、保存されたパスワードのクラックをさらに困難にするために、さらなる予防策を講じることが推奨されます。
ハッシュの問題点は、同じ入力に対して出力(=ハッシュ)が常に同じであることです。そのため、ハッシュは予測可能であり、したがって脆弱なのです。この問題は、ハッシュ化する際に入力文字列と一緒に追加のランダムな文字列を渡すことで解決することができる。そうすれば、ハッシュは入力と同じ文字列を得るたびに同じ出力を生成することがなくなります。
ハッシュ化する際に入力文字列と一緒に渡される固定長の疑似ランダム文字列は、ソルトと呼ばれます。データベースにパスワードを保存する際には、毎回新しいランダムなソルトが作成され、パスワードと一緒にハッシュ関数に渡される。その結果、2人のユーザーが同じパスワードを持っていても、データベースへの記録は全く異なるものになります。
ハッシュ化する前に文字列の末尾に1文字を追加すると、ハッシュが完全に変わってしまうことを覚えておいてください。
パスワードの生成に使用されたソルトは別途保存され、ハッシュ化される新しい入力に追加されてデータベースに保存されたハッシュと比較されるため、ランダムな要素が追加されても、ユーザーはそれぞれのパスワードを使ってログインできることを保証します。ソルティングの目的は、1つのパスワードを解読するのに必要な計算量を大幅に削減することではなく、ハッシュ化された文字列の間に類似性を見出し、攻撃者が複数のパスワードを同じものとして解読するのを防ぐことです。
ソルティングによって、極めて計算量の多い操作が1つのインスタンスに局所化され、データベース内のすべてのパスワードに対して繰り返されることになり、セキュリティが破られる連鎖を止めることができるのです。
ありがたいことに、このロジックの全体は、セキュリティフレームワークとモジュールによって抽象化されており、コード内で簡単に使用することができます。
BCryptとは何ですか?
BCryptはパスワードハッシュアルゴリズムであり、これまで述べてきたようなセキュリティ上の注意点を考慮して設計されています。セキュリティに特化したオープンソースのOSであるOpenBSDのデフォルトのパスワードハッシュアルゴリズムとして使用されており、現在までに最も広くサポートされているハッシュアルゴリズムです。
BCryptはかなり安全だと考えられている。BCryptのハッシュ機能はBlowfish(暗号)アルゴリズムをベースにしており、saltingとadaptive computation speedを実装しています。適応的な計算速度とは、ハッシュ値の計算の複雑性を高めることで、アルゴリズムの将来性を担保するものである。ハードウェアの計算速度が向上しても、ブルートフォースアタックを防ぐために十分な速度が維持されます。
BCryptは広くサポートされており、ほとんどの主要な言語で実装されている。Java、JavaScript、C、C++、C#、Go、Perl、PHPなど、一般的に利用可能な実装が存在します。このガイドでは、BCryptアルゴリズムのPython実装を取り上げます。
BCrypt を使って Python でパスワードをハッシュ化する方法
PyPi の bcrypt
モジュールは BCrypt の素晴らしい実装を提供しており、pip
を使って簡単にインストールすることができます。
$ pip install bcrypt
注意
必要な依存関係がすべてインストールされていることを確認するために、公式ドキュメントでは、選択したオペレーティングシステムに基づいて、以下のコマンドを実行するよう助言しています。
DebianとUbuntuの場合。
$ sudo apt-get install build-essential libffi-dev python-dev
Fedora と RHEL-derivatives の場合。
$ sudo yum install gcc libffi-devel python-devel
Alpineの場合。
$ apk add --update musl-dev gcc libffi-dev
BCryptをpip`でインストールした後、プロジェクトにインポートすることができます。
import bcrypt
BCryptを使ってパスワードをハッシュ化するには、まず、パスワードをバイトの配列に変換する必要があります。そのためには、 string
クラスの encode()
メソッドを使用します! これは、ハッシュしたいパスワードの文字列版を、あるエンコード形式を与えてバイト配列にエンコードし、BCrypt を使ってハッシュできるようにするものです。
ここでは、'MyPassWord'
をパスワードの例として、BCrypt の使用法を説明する。
pwd = 'MyPassWord'
bytePwd = password.encode('utf-8')
encode()`メソッドは、あるエンコーディング(例えば、ASCII、UTF-8など)の文字列を受け取り、それに対応するバイト配列に変換する。文字列から形成されるバイト配列はb-stringと呼ばれます。
注意: 先ほどの例では、pwd
は文字列で、bytePwd
はバイト配列です。しかし、両方の変数を表示した場合、目に見える違いは bytePwd
の値の前に b
という接頭辞があることだけです – b'myPassword'
. このことから、このバイト配列の名前は b-string と呼ばれるようになりました。
最後に、エンコードされたパスワードを BCrypt を使ってハッシュ化することができます。
# Generate salt
mySalt = bcrypt.gensalt()
# Hash password
hash = bcrypt.hashpw(bytePwd, mySalt)
ご覧のように、BCryptでハッシュ化に使用されるメソッドは hashpw()
です。これは2つの引数、パスワードのb-string表現とソルトを受け取ります。もちろん、手動でソルトを作成することもできますが、代わりに gensalt()
メソッドを使用することが推奨されます。これは、暗号的に安全な方法でソルトを作成するために特別に作られたBCryptのメソッドです。
注:BCryptの適応的な計算速度は、ソルトの作成に必要な反復回数を設定することで実現されます。この値は gensalt()
メソッドの引数として渡される。デフォルト値は12で、BCryptはソルトを生成するために212 (4096)回の反復を行うことを意味します。この引数の値を大きくすることで、ソルトの生成に使用する反復回数を増やし、ひいてはハッシュの計算に必要な時間を増やすことができます。
さて、 hash
にはパスワード pwd
をハッシュ化したものが格納されています。この hash
はなんとなく似ているはずです。
b'$2b$12$1XCXpgmbzURJvo.bA5m58OSE4qhe6pukgSRMrxI9aNSlePy06FuTi'
元のパスワードとあまり似ていませんよね?しかし、BCryptの checkpw()
メソッドを使って hash
と元のパスワードを比較すると、 True
値が返されます!
注意: checkpw()
メソッドは、ハッシュ化されたパスワードを検証するために設計されています。新しい入力パスワードをハッシュ化し、自動的に追跡するソルトを追加して、その結果を比較します。
ここでは、リテラルテキスト password
が、先ほど作成した新しい hash
に対して有効なパスワードであるかどうかをチェックしてみましょう。
print(bcrypt.checkpw(password, hash))
# Output: True
BCrypt出力の構成要素
前の例で見たように、BCryptへの入力はパスワード(最大72バイト)とソルト(それに伴う反復回数)であり、出力は24バイトのハッシュである。
BCryptがどのようにハッシュを生成しているかを把握するために、次の図をみてみましょう。
この図はパスワード「MyPassword」をハッシュ化したもので、前節で説明したハッシュ化を表しています。
前に説明したように、gensalt()
メソッドを呼び出すたびに、新しい固定サイズのバイト配列(b-stringで表される)が生成されます。この例では、gensalt()
メソッドは図中の salt
とマークされた出力を生成します。ここで、salt
の部分を分解して、それぞれのサブセクションを説明しましょう。
saltは
$` 記号で区切られた3つのサブセクションから構成されています。
- bcrypt バージョン
特別なハッシュアルゴリズムの識別子で、この場合は 2b
– BCrypt アルゴリズムの最新バージョンです。
* 指数
gensalt()` メソッドの引数で、ソルトを計算するために使用する反復回数を表します。引数が渡されない場合、デフォルト値は12であり、したがってソルトを計算するために212回の繰り返しが使用されます。
* 生成されるソルト
22文字で表される生成されたソルトの基数64エンコーディング。
その後、BCryptは salt
と MyPassword
のハッシュ値をくっつけて、 MyPassword
の最終的な hash
を生成する。
注: MyPassword
(または他のパスワード)のハッシュ値は、24バイトのハッシュの最初の23バイトをradix-64でエンコードしたものを指しています。これは31文字で表される。
結論
この記事を読むと、データベースに保存する前にBCryptを使ってパスワードをハッシュ化する方法について、しっかりと理解することができます。物事を整理するために、一般的な意味での基本的な用語を説明し、次にBCryptの例でパスワードのハッシュ化のプロセスを説明しました。