Pythonプログラマーの間で広く活用されている技術がハッシュ化です。ハッシュ化技術を使って入力データを一定のサイズの値に変換することができます。この値はデータを一意に表すもので、ハッシュ化を使って様々な形式のデータを安全性を高めながら送信・保存することが可能です。
ハッシュ化は不正アクセスや改ざんからデータを保護する役割を果たします。データインテグリティやセキュリティの面で欠かせない要素です。
この記事では、Pythonのハッシュそしてハッシュ化技術について知っておくべき点を一通りご説明します。そして、ハッシュ化の用途を掘り下げ、コードを効率的で、安全で、信頼できるものにする様々なハッシュ化アルゴリズムに迫りたいと思います。
Pythonのハッシュ化とは
ハッシュ化とは文字列、ファイル、オブジェクトなどの入力データを固定サイズの値に変換することを意味します。この操作を行うことで、ハッシュ値という固有かつ再現可能な出力結果が得られます。
ハッシュはデータに実行された操作を検出し、セキュリティを強化する上で重要な役割を果たします。ファイル、メッセージ、その他のデータに対してハッシュ化を適用することができます。アプリケーションであれば、例えば、ハッシュ値を安全に保存し、後からそれを照合することでデータが改ざんされていないことを確認可能です。
セキュリティにおけるハッシュ化の最も一般的な用途の一つが、パスワードの保存です。ハッシュ化は、プレーンテキストのパスワードをデータベースに保存する代わりに有効な手段となります。ユーザーがパスワードを入力すると、システムがそれをハッシュ化してからデータベースに保存します。ハッカーが万が一データベースにアクセスした場合であっても、パスワードを盗むことは難しくなります。
これらのことを可能にするのが、Pythonのハッシュ関数です。この関数によって、データをハッシュ値へと変換する操作を行うことができます。
ハッシュ化に欠かせない要素
ハッシュ関数が効果的で安全であるためには、以下の基準を満たす必要があります。
- 一貫性:同じ入力が与えられた場合、関数は常に同じ出力を返すこと。
- 効率性:与えられた入力のハッシュ値を計算する際に、計算効率が良いこと。
- 耐衝突性:関数は、2つの入力が同じハッシュ値を作る可能性を最小化する必要がある。
- 一様性:関数の出力は、ハッシュ値の取り得る範囲に一様に分布していなければならない。
- 非可逆性:コンピュータがハッシュ値に基づいて関数の入力値を推定できる可能性は低くなければならない。
- 予測不可能性:一連の入力が与えられた場合、関数の出力を予測することは困難でなければならない。
- 入力の変化に対する敏感性:関数は入力のわずかな違いにも敏感でなければならない。わずかな変化でも、結果のハッシュ値に大きな違いを生じさせるべきである。
ハッシュの使用例
以上の特徴をすべて備えたハッシュ関数を利用し、様々な用途に適用することができます。ハッシュ関数は以下のような目的に有効です。
- パスワードの保存:ハッシュは、最新のあらゆるシステムでユーザーのパスワードを保存するのに使用されています。Pythonは様々なモジュールを組み合わせて、パスワードをデータベースに保存する前にハッシュ化することができます。
- キャッシュ:ハッシュ化では、後で呼び出す時間を削減するために関数の出力を保存できます。
- データ検索:Pythonでは、辞書データ構造を組み込んだハッシュテーブルを使用することで、キーによって値をすばやく検索可能です。
- デジタル署名:ハッシュを活用して、デジタル署名を持つメッセージの真正性を検証することができます。
- ファイルの整合性チェック:ハッシュは、転送中やダウンロード中にファイルの整合性をチェックすることができます。
Pythonの組み込みハッシュ関数
Pythonの組み込みハッシュ関数、hash()
は、入力オブジェクトを表す整数値を返します。その後、コード内では結果のハッシュ値を使用して、ハッシュテーブル内のオブジェクトの位置が特定されます。このハッシュテーブルは、辞書と集合を実装したデータ構造になっています。
以下のコードは、hash()
関数の動作を示したものです
my_string = "hello world"
# 文字列のハッシュ値を計算
hash_value = hash(my_string)
# 文字列とそのハッシュ値を表示
print("String: ", my_string)
print("Hash value: ", hash_value)
このコードをhash.pyという名前のファイルに保存すると、次のように実行(そして出力を見ることが)できます。
% python3 hash.py
String: hello world
Hash value: 2213812294562653681
もう一度実行してみましょう。
% python3 hash.py
String: hello world
Hash value: -631897764808734609
Pythonの最近のリリース(バージョン3.3以降)では、デフォルトでこの関数にランダムなハッシュシードが適用されるため、2回目の実行時にはハッシュ値が異なるものになっています。このシードはPythonを起動するたびに変わります。一つのインスタンス内では、結果は同じになります。
例えば、このコードをhash.pyファイルに書いてみましょう。
my_string = "hello world"
# 文字列の2つのハッシュ値を計算
hash_value1 = hash(my_string)
hash_value2 = hash(my_string)
# 文字列とそのハッシュ値を表示
print("String: ", my_string)
print("Hash value 1: ", hash_value1)
print("Hash value 2: ", hash_value2)
実行すると次のようになります。
String: hello world
Hash value 1: -7779434013116951864
Hash value 2: -7779434013116951864
ハッシュ化の限界
Pythonのハッシュ関数は様々な用途で使えますが、その限界から単純にセキュリティ強化のために使うのは得策とは言えません。詳しい理由は以下の通りです。
- 衝突(コリジョン)攻撃:2つの異なる入力が同じハッシュ値を生成してしまう状況が衝突と呼ばれます。この点(脆弱性)に目を付けることで、認証やデータの完全性チェックにおいてハッシュ値に依存するセキュリティ強化策をバイパスしようとする攻撃が存在します。
- 入力サイズの制限:ハッシュ関数は入力される情報のサイズに関係なく一定のサイズの出力を生成するため、ハッシュ関数の出力よりも大きなサイズの入力は衝突を引き起こす可能性があります。
- 予測可能性:ハッシュ関数は決定論的(一貫性を確保するもの)であるべきで、同じ入力を提供するたびに同じ出力が返されます。攻撃者はこの特徴(ここでは弱点)を利用して、多くの入力に対するハッシュ値を事前にコンパイルしておき、ターゲットとする値のハッシュと比較して一致するものを見つけることができる可能性があります。この手法はレインボーテーブル攻撃と呼ばれています。
攻撃を防ぎ、データを安全に保つには、このような脆弱性に対抗できるように設計されたセキュアなハッシュアルゴリズムを使うことです。
Pythonのセキュアなハッシュ化にhashlibを使う
Python組み込みのhash()
を使う代わりに、hashlibを使ってより安全にハッシュ化を行うことができます。このPythonモジュールには、データを安全にハッシュ化するためのさまざまなハッシュアルゴリズムがあります。例えば、MD5、SHA-1に加え、さらに安全なSHA-256、SHA-384、SHA-512などのSHA-2規格が含まれます。
MD5
広く使われている暗号アルゴリズムMD5は、128ビットのハッシュ値を生成します。以下のようなコードで、hashlibのmd5
コンストラクタを使ってMD5ハッシュ値を作ることができます。
import hashlib
text = "Hello World"
hash_object = hashlib.md5(text.encode())
print(hash_object.hexdigest())
上記(hash.pyファイル)の出力は一貫したものになります。
b10a8db164e0754105b7a99be72e3fe5
補足)上記のコードのhexdigest()
メソッドは、バイナリ以外の形式(メールなど)でも安全な16進数形式でハッシュ値を返すものです。
SHA-1
SHA-1ハッシュ関数は、160ビットのハッシュ値を作ることでデータを保護します。hashlibモジュールのSHA-1ハッシュを利用するには、sha1
コンストラクタで以下のようにコードを記述することができます。
import hashlib
text = "Hello World"
hash_object = hashlib.sha1(text.encode())
print(hash_object.hexdigest())
上記の出力は以下のようになります。
0a4d55a8d778e5022fab701977c5d840bbc486d0
SHA-256
SHA-2には様々なハッシュオプションがあります。hashlib SHA-256コンストラクタは、256ビットのより安全性の高い値を生成します。
デジタル署名やメッセージ認証コードのような暗号にSHA-256がよく使われています。以下のコードは、SHA-256ハッシュ生成の方法を示しています。
import hashlib
text = "Hello World"
hash_object = hashlib.sha256(text.encode())
print(hash_object.hexdigest())
上記の出力は以下のようになります。
a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e
SHA-384
SHA-384は384ビットのハッシュ値です。高いデータセキュリティが必要なアプリケーションでSHA-384関数がよく使用されます。
これまでの例と同様に、以下がSHA-384ハッシュを生成するコードです。
hash_object = hashlib.sha384(text.encode())
SHA-512
SHA-512はSHA-2規格の中でも最も安全性の高い選択肢です。512ビットのハッシュ値が生成されます。データの完全性をチェックするような高スループットのアプリケーションでよく使用されます。以下のコードは、Pythonのhashlibモジュールを使ってSHA-512ハッシュを生成する方法です。
hash_object = hashlib.sha512(text.encode())
ハッシュアルゴリズムの選び方
これらのアルゴリズムは異なるので、使用するケースとそのセキュリティ要件に基づいてハッシュアルゴリズムを選択するようにしてください。以下にいくつかの手順を示します。
- 用途を明確にする:用途によって使用するアルゴリズムの種類が決まります。例えば、パスワードのような機密データを保存する場合、ハッシュアルゴリズムでブルートフォース攻撃に対する守りをかためる必要があります。
- セキュリティ要件を考慮する:セキュリティ要件は、保存するデータの種類によって異なり、それによってどのようなアルゴリズムを選ぶべきかが決まります。例えば、機密性の高い情報を保存する場合は、堅牢なハッシュアルゴリズムが欠かせません。
- 利用可能なハッシュアルゴリズムを調査する:各ハッシュタイプを調査し、その長所と短所を理解しましょう。この情報をもとにして、用途に最適なオプションを絞り込んでいくことができます。
- 選択したハッシュアルゴリズムを評価する:ハッシュアルゴリズムを選択したら、それがセキュリティ要件を満たしているかどうかを評価します。既知の攻撃や脆弱性に対するテストなどがこれに含まれます。
- ハッシュアルゴリズムの実装とテスト:最後に、アルゴリズムを実装して徹底的にテストし、正しく安全に機能することを確認します。
パスワード保存にハッシュを使う方法
ハッシュは、サイバーセキュリティの重要な要素である「パスワードの保存」に関連して多くの可能性を秘めています。
理想的には、不正アクセスやデータ漏洩を防ぐために、アプリケーションのパスワードをハッシュ化し、安全なデータベースに保存すべきです。しかし、ハッシュ化だけでは情報を保護するには不十分かもしれません。ハッシュ化したパスワードは、ブルートフォース攻撃やテーブルを参照するかたちでの攻撃を受けやすい傾向にあります。多くのサイバー攻撃で、これらの手法を利用してパスワードの推測やアカウントへの不正アクセスが試みられています。
パスワードの保存にハッシュを使うより安全な方法として、ソルト(salting)があります。ソルトは、ハッシュ化前に、パスワードに固有のランダムな文字列を追加する手法です。ソルトは各パスワードに固有で、アプリケーションにより、ハッシュ化したパスワードとソルトがデータベースに保存されます。
ユーザーがログインするたびに、データベースからソルトを取り出し、入力されたパスワードに追加し、ソルトとパスワードを組み合わせたものをハッシュ化する流れになります。
攻撃者がデータベースに不正アクセスした場合であっても、各パスワードと可能性のある各ソルト値のハッシュを計算する作業が必要になります。ソルトを用いることで、このような攻撃が一気に複雑化するので、攻撃そのものを抑止するのに効果的です。
Pythonのsecretsモジュールを使って簡単にソルトを追加することができます。このモジュールによりランダムなソルトが生成され、パスワードを安全に保存しながらトークンと暗号鍵を管理可能です。
以下のコードでは、hashlibライブラリとsecretsモジュールを使って、ユーザーパスワードの安全性を高めています。
import hashlib
import secrets
# secretsモジュールを使ってランダムなソルトを生成
salt = secrets.token_hex(16)
# 入力内容からユーザーのパスワードを取得
password = input("Enter your password: ")
# ソルトとSHA-256アルゴリズムを使ってパスワードをハッシュ化
hash_object = hashlib.sha256((password + salt).encode())
# ハッシュの16進数値を取得
hash_hex = hash_object.hexdigest()
# ソルトと16進数ハッシュ値をデータベースに保存
データの完全性チェックにハッシュを使う方法
ハッシュは、データの完全性をチェックし、送信データを変更や改ざんから保護するのにも役立ちます。以下の4段階の手法にならい、暗号化ハッシュ関数を使うことで、ファイルに一意のハッシュ値を与えることができます。
まず、ハッシュ関数を選択し、それを使って入力データのハッシュ値を生成します。そのハッシュ値を保存し、必要なときに比較に使用します。データの完全性を検証する必要があるときはいつでも、同じハッシュ関数を使用してデータのハッシュ値が生成できます。その後、新しいハッシュ値と保存されている値を比較し、両者が同一であることを確認するという流れです。同一であれば、データは破損していないことがわかります。
ハッシュ化した値は一意であり、入力データにわずかな変化があっただけでも、ハッシュ値は大きく変化します。これによって、送信データに対する不正な変更や修正を簡単に検出することができます。
以下に、データの完全性チェックにハッシュ関数を使用する例を示します。
ステップ1. hashlibモジュールのインポート
import hashlib
ステップ2. hashlibハッシュアルゴリズムの使用
def generate_hash(file_path):
# バイナリモードでファイルを開く
with open(file_path, "rb") as f:
# ファイルの内容を読み取る
contents = f.read()
# コンテンツのSHA-256ハッシュ値を生成する
hash_object = hashlib.sha256(contents)
# ハッシュの16進数を返す
return hash_object.hexdigest()
ステップ3. 関数を呼び出してファイルパスを渡す
file_path = "path/to/my/file.txt"
hash_value = generate_hash(file_path)
print(hash_value)
ステップ 4. オリジナルファイルと送信または変更されたファイルのハッシュを生成する
# 元ファイルのハッシュを生成
original_file_path = "path/to/my/file.txt"
original_file_hash = generate_hash(original_file_path)
# ファイルの送信または変更(別の場所にコピーするなど)
transmitted_file_path = "path/to/transmitted/file.txt"
# 送信ファイルのハッシュを生成
transmitted_file_hash = generate_hash(transmitted_file_path)
ステップ5. 2つのハッシュを比較する
if original_file_hash == transmitted_file_hash:
print("The file has not been tampered with")
else:
print("The file has been tampered with")
まとめ
ハッシュはデータの完全性とパスワードのセキュリティにとって非常に重要なものです。ハッシュ関数を最大限に活用するには、hashlibモジュールの使用やソルトの処理など、ハッシュ技術を実装する必要があります。
これらの工夫は、レインボーテーブル攻撃や衝突(コリジョン)攻撃など、ハッシュに影響するセキュリティの脆弱性を防ぐのに役立ちます。実際に、ファイルのデータの完全性を保証したり、パスワードを安全に保存したりするために、Pythonのハッシュ関数でこれらのテクニックがよく使われます。
Pythonのハッシュ技術について学んだところで、続いてはご自分のアプリケーションのセキュリティ向上にご活用ください。Kinstaでは他にも数多くの記事を公開しています。Pythonの記事についてはこちらをご覧ください。そして、Pythonアプリケーションのデプロイには、Kinstaのアプリケーションホスティングプラットフォームをご検討ください。
コメントを残す