1. 要約
-
通信路にパスワードを平文で流さないために、一時的に発行される乱数を使って暗号を作成し、その暗号をもって「認証」を行う
-
IC乗車券や、車のカギを遠隔で開錠できるキーレスエントリなどで用いられる技術である
2. チャレンジレスポンス認証
2.1. チャレンジレスポンス認証とは?
チャレンジレスポンス認証とは、IC乗車券やCHAPで利用される。
- 利用者側が初めにサーバーへアクセス要求をすると、サーバー側は時限的に利用できる乱数(=チャレンジ)を生成し、利用者へ送る
- 利用者はあらかじめ設定されたパスワードと、サーバー側から送られてきたチャレンジを組合わせたレスポンスを生成する。このレスポンスをサーバー側へ送信する
- サーバー側は保存されている利用者のパスワードと生成されたチャレンジからレスポンスを生成する
- サーバー側で生成されたレスポンスと、利用者がサーバーへ送信したレスポンスを照らし合わせて認証を行う
- 認証結果を返す
CHAPとは?
コンピュータネットワークにおいて、Challenge-Handshake Authentication Protocol (チャレンジ・ハンドシェイク・オーセンティケーション・プロトコル、CHAP) は、ユーザやネットワークホストに対する認証プロトコルである。例えば、この認証はインターネットサービスプロバイダによって行われる。
RFC 1994: PPP Challenge Handshake Authentication Protocol (CHAP) がこのプロトコルを定義している。
CHAP は Point-to-Point Protocol (PPP) が、リモートクライアントの正当性を確認するための認証方法として使用される。CHAP は3ウェイ・ハンドシェイクによって、定期的にクライアントの正当性を確認する。これは、最初のデータ通信リンクを確立するときに行われ、その後はいつでも行われる可能性がある。この確認は、共有秘密 (例えばクライアントユーザのパスワード) に基づいている。
Wikipedia: Challenge-Handshake Authentication Protocolより
https://ja.wikipedia.org/wiki/Challenge-Handshake_Authentication_Protocol
2.2. Pythonコード(チャレンジレスポンス認証)
チャレンジレスポンスの一連の流れをPythonで書くと以下のようになる。
import hashlib
import random
class Server:
def __init__(self, password):
self.hashed_password = self.get_hash(password)
self.challenge = None
def get_response(self, password):
return self.get_hash(password + str(self.challenge))
def get_challenge(self):
self.challenge = str(random.randint(10000, 99999))
return self.challenge
def _verify(self, response):
if not self.challenge:
raise ValueError("チャレンジが未発行 or すでに使われました")
expected = self.get_hash(self.hashed_password + self.challenge)
result = (response == expected)
self.challenge = None # チャレンジを無効にする
return result
def get_hash(self, x):
return hashlib.sha256(x.encode()).hexdigest() # 文字列を SHA256 でハッシュ化
def get_result(self, response):
try:
return "🟢認証成功" if self._verify(response) else "🔴認証失敗"
except ValueError as e:
return f"⚠️エラー: {e}"
def client_response(password, challenge):
hashed_password = hashlib.sha256(password.encode()).hexdigest()
return hashlib.sha256((hashed_password + challenge).encode()).hexdigest()
def main():
correct_password = "1079"
incorrect_password = "3579"
# 事前に登録する
cr = Server(correct_password)
# 認証
challenge = cr.get_challenge()
# 正しいパスワードのレスポンス
correct_response = client_response(correct_password, challenge)
print(cr.get_result(correct_response)) # 🟢認証成功
# 認証
incorrect_challenge = cr.get_challenge()
# 誤ったパスワードのレスポンス
incorrect_response = client_response(incorrect_password, incorrect_challenge)
print(cr.get_result(incorrect_response)) # 🔴認証失敗
if __name__ == "__main__":
main()
このPythonコードでは、ハッシュ値の生成にPythonの標準ライブラリであるhashlibモジュールを、乱数(=チャレンジ)の生成に同じくPythonの標準ライブラリであるrandomモジュールを利用している。
Server
オブジェクトは、予め正しいパスワードを保存する必要がある。(サーバー側でもレスポンスを生成する必要があるため)その後 get_challenge
メソッドを使いチャレンジを生成する。ここで生成したチャレンジと、認証したいパスワードから client_response``を利用してレスポンスを作成している。それを
Serverオブジェクト内部のメソッドである
get_result` を利用して認証が成功したか、失敗したかを返す。
サーバー側のレスポンスを作成する _verify
は、サーバー内部で利用されるべきであり、外部から触れないようにするため、Pythonでオブジェクト内部インターフェースを意味する _
(アンダースコア)を使っている。C++やJavaなどでは、privateを使うのがいいだろう。
3. チャレンジレスポンス認証のメリットは?
- チャレンジレスポンス認証は、生成されたレスポンスが不可逆(=暗号から元のパスワードを割り出せない)である
- パスワードの平文(=元のパスワード)を通信経路に流すことなく認証を行うことができる
- チャレンジはその都度生成されるので、ネットワーク上から盗聴したパスワードは再利用することができない
- 認証処理は一方向ハッシュ関数に依存しているため、計算リソースは軽く、サーバー側の実装も簡潔に保てる
4. 終わりに
もし間違いがあれば、コメントで教えてください。