LoginSignup
9
11

More than 5 years have passed since last update.

Pythonでパスワードの強度をしらべるライブラリSafeのコードリーディング

Last updated at Posted at 2015-11-08

Safeはパスワードをの強度をしらべるためのライブラリです。

https://pypi.python.org/pypi/Safe/
https://github.com/lepture/safe

INSTALL

pipでインストールできます。

$ pip install safe

READMEのsampleコードを試す

とりあえず safe をimportしておきます。

>>> import safe

safe.check() 関数で強度をしらべます。

>>> safe.check(1)
terrible
>>> safe.check('password')
simple
>>> safe.check('is.safe')
terrible
>>> safe.check('x*V-92Ba')
strong

返値はsafe.Strength()のインスタンスです。

>>> type(safe.check('x*V-92Ba'))
<class 'safe.Strength'>

結果はbool()に変換できます。

>>> bool(safe.check('x*V-92Ba'))
True
>>> bool(safe.check('a'))
False

コードリーディング

コードを読んでいきます。

safe.check() - パスワードの強度をチェックする

safe.check() 関数はパスワードの強度を調べます。

引数

raw, length, freq, min_types, levelを引数に渡せます。

rawは調べたいパスワードの文字列です。通常はstrを渡すのですが、関数ないでto_unicode()しており、intを渡してもstrに変換して処理したりします。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L156

to_unicode()は_compat.pyで定義されています。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/_compat.py#L28-L38

lengthは必要なパスワード長です。freqはよくあるパスワードをしらべるときに使います。

min_typesは文字の種類の数がいくつ含まれているべきかを指定します。例えばmin_types=3でlength=3の場合1qAはOK(数字/小文字/大文字)、1qaはNG(数字/小文字)です。

>>> bool(safe.check('1qA', length=3, min_types=3))
True
>>> bool(safe.check('1qa', length=3, min_types=3))
False

levelはそのパスワードの強度がvalidがinvalidかどうかの閾値です。入力値はint/floatが許容されそうですが、STRONG以上の数値を指定してもSTRONGに戻されます(そうしないと常にinvalidになってしまいますからね...)。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L157-L158

levelは次のように定義されています。

TERRIBLE = 0
SIMPLE = 1
MEDIUM = 2
STRONG = 3

返値

戻り値はsafe.Strengthのインスタンスです。safe.Strengthvalidstrengthmessageの属性を持っています。

validはbool値です。Trueがvalidです。strengthは強度を示す文字列です。strengthに入る文字列は次のいずれかです。

streangthに用いられる文字列 意味
strong つよい
medium まあまあ
simple 簡単
terrible 鼻クソ(意訳)

(ちなみにこの文字列はsafe.Strengthのインスタンス生成時のコードにハードコーディングされちゃっていますorz)

messageはなぜそう判断されたかを表すメッセージです。

この関数は例えば1文字でsafe.is_asdf()に引っかかるような文字列を渡すと正しく結果を返します。

>>> safe.check('a', length=0, min_types=0)
simple

しかし1文字でsafe.is_asdf()に引っかからないような文字列を渡すとsafe.is_by_step()が送出した例外をハンドリングせず流してしまいます。

>>> safe.check('(', length=0, min_types=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/sximada/ng/env/lib/python3.4/site-packages/safe/__init__.py", line 163, in check
    if is_asdf(raw) or is_by_step(raw):
  File "/home/sximada/ng/env/lib/python3.4/site-packages/safe/__init__.py", line 89, in is_by_step
    delta = ord(raw[1]) - ord(raw[0])
IndexError: string index out of range

それは条件チェックの時に正しくエラーハンドリングされていないからです。
safe.is_by_step()は後述しますが1文字の文字列を渡す時はIndexError例外を送出してきます。safe.is_asdf()がTrueにならない文字列(たとえば()の場合は、1文字(がsafe.is_by_step()に渡ってしまいます。

    if is_asdf(raw) or is_by_step(raw):
        return Strength(False, 'simple', 'password has a pattern')

とはいえ1文字のチェックを許可するケースなんて稀なのであんまり神経質にならなくてもいいところな気もします。

safe.is_asdf() - キーボードの並び順かどうかを判定する

与えられた文字列がキーボードの並び順になっているかどうかを判定します。
ここで並び順と言っているものは次の箇所で定義された文字列のことです。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L69
そのため全てのキーボードの並び順のパターンを検査しているわけではありません。
(ちょっと少なすぎじゃね?)

引数

rawは判定する文字列です。rawで指定した文字列とその並び順を反対にした文字列が判定の対象になります。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L75

戻り値

Bool値です。キーボードの並び順になっている場合、Trueを返します。

safe.is_by_step() - シフト暗号っぽい文字列かどうかを判定する

シフト暗号とはn文字ずつずらすという古典的で愛すべき(でも絶対使っちゃいけない)暗号方式です。中でもカエサル暗号は有名で3文字づつずらすというものです。例えばtestという文字列を3文字づつずらすとwhvwという文字列ができます。このような文字列はあっというまにクラックされるので、隠した気になるだけで、何の意味もないです。
この関数ではそういう文字列かどうかを判定しています。

指定した文字列rawの1文字目と0文字目の距離を測っています。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L89

その後全ての文字がその距離通りにシフトされているかどうかを判定しています。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L91-L95

長さ1の文字列を渡すとIndexErrorを送出してきます。

>>> safe.is_by_step('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/sximada/ng/env/lib/python3.4/site-packages/safe/__init__.py", line 89, in is_by_step
    delta = ord(raw[1]) - ord(raw[0])
IndexError: string index out of range

引数

rawは判定する文字列です。

返値

bool値です。シフト暗号っぽい文字列ならTrueです。

safe.is_common_password() - よく用いられるパスワードかどうかを判定する

docstringによると https://xato.net/passwords/more-top-worst-passwords/ にあるよくあるパスワード1000を用いているという記述がありますが、残念なことにこのURLは404 Not Foundでした(おい!!)。

よく使われるパスワードのリストはpackage内に内包されています。デフォルトではこのリストが使われます。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/words.dat

このファイルは次のようなフォーマットをしています。

文字列 頻出レベル

頻出レベルとは判定するために使われる整数で値が大きいほどよく使われることを表します。

このファイルはカスタマイズしたファイルを用いることもできます。その場合は環境変数PYTHON_SAFE_WORDS_FILEにカスタマイズしたファイルのパスを指定すればそちらを使います。

このファイルのデータは使用時には {'文字列': 整数, ...}の形式のdictとして使用されます。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L58-L61

その後、そのdictをキャッシュするのですが、そのキャッシュファイルの場所を環境変数PYTHON_SAFE_WORDS_CACHEで指定できます。

このデータはsafeパッケージをimportする際にファイルをreadしてsafe.WORDSにそのデータを保持します。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L68

そのためPYTHON_SAFE_WORDS_FILEを指定していて、そのファイルがない場合はFileNotFoundErrorが送出され、importに失敗するためるでしょう(若干辛い仕様)。

引数

rawは判定する文字列です。
freqはよく使われるレベルという感じです。数字が大きいほどよく使われることを表します。

返値

bool値です。よく使われる文字列の場合はTrueです。引数freqで指定した数値以下の頻出レベルの場合はFalseです。よく使われる文字列に該当しない場合もFalseです。

safe.safety() - 公開されていないけど公開utilityっぽい関数

safe.safety()はsafe.check()をcallしてその戻り値を返すだけの関数です。
両者の違いは引数のデフォルト値です。safe.check()のmin_typesが3なのに対して、safe.safety()は2です。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L192-L193

しかしこの関数はallの中には含まれていません。そしてpackageの内部でも使っていません。
https://github.com/lepture/safe/blob/cb554022df8be8a4f7fa0b8fbee39639b4819495/safe/__init__.py#L20-L23
含め忘れなのか、それとも廃止していきたいという作者の意図があってあえて入れていないのか(そうだとしてもそういうやり方はして欲しくないけど...)、何れにしてもこの関数は使わずsafe.check()を使って行ったほうがよさそうです。(ちなみにこの作者はcompat.pyでもall_を割と律儀に書いているので、書き忘れ臭がします。)

あとがき

このライブラリは200行程度しかない小さなライブラリです。
シンプルにできているので使い勝手はとても良さそうでした。
この記事を書くのに2時間程度使いました。 -> 1時間100行ぐらいかな

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11