35.5. crypt — Unix パスワードをチェックするための関数

ソースコード: Lib/crypt.py


このモジュールは修正 DES アルゴリズムに基づいた一方向ハッシュ関数である crypt(3) ルーチンを実装しています。詳細については Unix マニュアルページを参照してください。このモジュールは、実際に入力されたパスワードを記録することなくチェック出来るようにするためのハッシュ化パスワードを記録したり、Unix パスワードに (脆弱性検査のための) 辞書攻撃を試みるのに使えます。

このモジュールは実行環境の crypt(3) の実装に依存しています。そのため、現在の実装で利用可能な拡張を、このモジュールでもそのまま利用できます。

35.5.1. ハッシュ化方式

バージョン 3.3 で追加.

crypt モジュールはハッシュ化方式の一覧を定義しています (すべての方式がすべてのプラットフォームで使えるわけではありません):

crypt.METHOD_SHA512

16文字のソルトと86文字のハッシュ値を持つモジュラー暗号形式です。これが最も強い方式です。

crypt.METHOD_SHA256

16文字のソルトと43文字のハッシュ値を持つモジュラー暗号形式です。

crypt.METHOD_MD5

8文字のソルトと22文字のハッシュ値を持つモジュラー暗号形式です。

crypt.METHOD_CRYPT

2文字のソルトと13文字のハッシュ値を持つモジュラー暗号形式です。これが最も弱い方式です。

35.5.2. モジュール属性

バージョン 3.3 で追加.

crypt.methods

各アルゴリズムを crypt.METHOD_* オブジェクトとし、利用可能なパスワードのハッシュアルゴリズムのリストを返します。このリストは最も強いものから弱いものの順で並べられており、少なくとも crypt.METHOD_CRYPT オブジェクトが含まれていることは保証されています。

35.5.3. モジュール関数

crypt モジュールは以下の関数を定義しています:

crypt.crypt(word, salt=None)

通常 word はプロンプトか画面から入力されたユーザのパスワードです。オプションの salt は、 mksalt() が返す文字列か、(すべての方式がすべてのプラットフォームで使えるわけではありませんが) crypt.METHOD_* 値のどれか1つか、この関数から返されたソルトを含む暗号化されたパスワード全体です。 salt が与えられない場合は、(methods() で返されるもののうち) 最も強い方式が使われます。

通常は、生の文字列のパスワードを word として渡し、前回の crypt() を呼び出した結果と今回の呼び出しの結果が同じになることで、パスワードの確認を行います。

salt (2文字から16文字のランダムな文字列で、方式を示す $digit$ が先頭に付いているかもしれません) は、暗号化アルゴリズムにぶれを生じさせるために使われます。 salt に含まれる文字は、モジュラー暗号形式の先頭にある $digit$ を除いて、集合 [./a-zA-Z0-9] に含まれていなければいけません。

ハッシュ化されたパスワードを文字列として返します。それは salt と同じアルファベット文字から構成されます。

いくつかの拡張された crypt(3) は異なる値と salt の長さを許しているので、パスワードをチェックする際には crypt されたパスワード文字列全体を salt として渡すよう勧めます。

バージョン 3.3 で変更: 文字列に加え、 saltcrypt.METHOD_* 値も受け取るようになりました。

crypt.mksalt(method=None)

指定された方式のランダムに生成されたソルトを返します。 method が与えられなかった場合は、 methods() で返される方式のうち最も強いものが使われます。

返り値は、 crypt.METHOD_CRYPT のための長さ2の文字列か、 $digit$ の後に集合 [./a-zA-Z0-9] にあるランダムな16文字が続く長さ19の文字列で、 crypt()salt 引数として渡せるものです。

バージョン 3.3 で追加.

35.5.4. 使用例

典型的な使い方を簡単な例で示します (タイミング攻撃に晒されないように、一定時間の比較演算子を使う必要があり、 hmac.compare_digest() がこの目的にちょうど良いです):

import pwd
import crypt
import getpass
from hmac import compare_digest as compare_hash

def login():
    username = input('Python login: ')
    cryptedpasswd = pwd.getpwnam(username)[1]
    if cryptedpasswd:
        if cryptedpasswd == 'x' or cryptedpasswd == '*':
            raise ValueError('no support for shadow passwords')
        cleartext = getpass.getpass()
        return compare_hash(crypt.crypt(cleartext, cryptedpasswd), cryptedpasswd)
    else:
        return True

利用可能な方式のうち最も強い方式を使いパスワードのハッシュ値を生成し、元のパスワードと比較してチェックします:

import crypt
from hmac import compare_digest as compare_hash

hashed = crypt.crypt(plaintext)
if not compare_hash(hashed, crypt.crypt(plaintext, hashed)):
    raise ValueError("hashed version doesn't validate against original")