ʍoɹɐɥsのブ口グ

ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็็็็็็็็็็็็็็็ ส็็็็็็็็็็็

簡単にできるチャレンジ/レスポンス認証

 チャレンジ/レスポンス認証自体はとても簡単な認証方式だが、通信路に秘密文の平文を流すことなく認証できるのでけっこういろいろなところに使われている。有名どころではPPPのCHAPもそうだし、Digest認証もそうですね。

SSHもチャレンジ/レスポンス認証に対応しているらしい(SSH-1ではチャレンジ256bit、ハッシュはMD5)。というか、ハイブリッド暗号(公開鍵暗号+共有鍵暗号)において、セッション鍵を生成するまでの仕組みはチャレンジ/レスポンス認証とちょっと似てます。

素材

  • 良質なハッシュ関数
  • 良質な疑似乱数生成器(できれば天然のもの、再現不可能なソースをかけたエントロピープールを持つものが理想)

レシピ

 サーバは要求に応じてランダムな文字列(チャレンジ、広い意味ではNonceともいう)をクライアントに返し、クライアントはチャレンジと秘密文を混ぜてハッシュ値を返し、サーバが同じハッシュ関数で一致するか検証するだけ。コードにしても結構短い。

#!/usr/bin/env python
# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil; coding: utf-8; -*-

import os
import hashlib
import base64

try:
    from Crypto.Random import random
except ImportError:
    import random
    random.seed(os.urandom(16))


def gen_challenge(length=256) -> bytes:
    return bytes(random.randint(0, 255) for _ in range(length))

def hash_fn(secret: bytes, challenge: bytes) -> bytes:
    return hashlib.sha512(secret + challenge).digest()


if __name__ == '__main__':
    # 秘密のパスワード
    secret = 'hogehoge'.encode('ascii')

    # 1. クライアントはサーバにchallengeを要求する
    challenge = gen_challenge()
    print('challenge(base64):', base64.standard_b64encode(challenge))

    # 2. クライアントは受け取ったchallengeとsecret(パスワード等)を合わせたハッシュ値を返す
    user_input = input('password:')
    response = hash_fn(user_input.encode('ascii'), challenge)
    print('response(base64):', base64.standard_b64encode(response))

    # 3.サーバは返されたハッシュ値を確認し、一致すれば認証成功とする
    # challegeはクライアントがresponseといっしょに返しても問題ない
    success = hash_fn(secret, challenge) == response
    print(['login failed', 'login success'][success])

 ハッシュ関数にSHA512、疑似乱数生成器にPython Cryptography Toolkit(Crypto)のを使っている(無い場合は標準のrandom)。CHAP(RFC1994)ではMD5なのだが、今時MD5ではドン引きされてしまいそうだ。チャレンジが十分に長ければそんなに気にするほどでもないのかもしれない(それはそれで「十分に長い」とはどの程度か、という問題になるのだが…)。

チャレンジ/レスポンス認証は脆弱?

 素人目にはチャレンジ/レスポンス認証の仕組み自体には問題は無いように思われる。時代に合わせて正しく設計/運用するかどうかだと思う。チャレンジ/レスポンス認証の脆弱性が有名なのは主にマイクロソフトのPPP接続時のCHAP(MS-CHAP)によるもので、これってばチャレンジがなんと64bit(8バイト!)。ハッシュ関数はMD4。今の時代で見たら脆弱そのもの。チャレンジが256bit以上でハッシュ関数がSHA2またはSHA3系だったら今でも安全に運用できるのではないだろうか。

 気を付ける部分は「サーバが秘密文(パスワードの平文)を知っている」という前提があること。これは脆弱性ではなく、そういう前提で作られている。最近ではサーバでパスワード等は平文で持たずハッシュ化して持つのが普通なので平文は知らない。とはいえクライアントで同じハッシュ関数を先頭に噛ませればいけないこともないが、なんかダサい。それにサーバで使っているパスワードのハッシュ方法やソルトがバレてしまう。Digest認証ではそんな感じになっている*1


 グローバルIPアドレスオプション(WiMAX 2+用)を見ていたら、設定ドキュメントにちょっとPPP接続を匂わせることが書いてあったのでなんとなく。CHAPは信用していないらしく、パスワードは0000固定、たぶんそれより下のレイヤー(MACアドレスとかハードウェアアドレス)で認証していると思われる。低レベルではまだPPPとかCHAPが健在なのだなぁ。

*1:ただしDigest認証はソルトは無い