no-image

Python の bcrypt 実装色々

Python の bcrypt 実装にはいくつかあって、PyPI で検索すると単体で使うもの以外にも Flask 用、Django 用など様々なものがあります。

単体で使うには現在でもメンテが活発な bcrypt が良いと思いますが、導入する環境によっては libffi に依存する関係で pip install 中にビルドが通りません。環境を整えれば解決する話(apt なら install libffi-dev で OK)なのですが、共用サーバなのもあって他のもので解決できそうであればその方が良いと判断しました。

ここで注意しなければいけないのが、上に挙げた 3 つの bcrypt 実装は全て import bcrypt して使うことになっています。これはつまり、異なるライブラリなのに import する名前が bcrypt で同じなので、今どのライブラリを使っているのかが判断できなくて予想外の問題を起す可能性があるということです。

実際にこんな投稿がありました。

import bcrypt する場合は開発環境と運用環境の違いに十分注意しないと、一見動いているように見えてもこのようにハマる可能性が高いです。

bcrypt 3.1.4 から py-bcrypt 0.4 へ

gensalt() の引数の違い

試しに同じソースを使って、環境を bcrypt から py-bcrypt に変更してみました。

<type 'exceptions.TypeError'>: gensalt() got an unexpected keyword argument 'rounds' 
 args = ("gensalt() got an unexpected keyword argument 'rounds'",) 
 message = "gensalt() got an unexpected keyword argument 'rounds'"

gensalt() の引数が違うようです。

bcrypt の src/bcrypt/__init__.py を読んでみると、rounds と prefix があります。

def gensalt(rounds=12, prefix=b"2b"):
    if prefix not in (b"2a", b"2b"):
    raise ValueError("Supported prefixes are b'2a' or b'2b'")

    if rounds < 4 or rounds > 31:
    raise ValueError("Invalid rounds")

    salt = os.urandom(16)
    output = _bcrypt.ffi.new("char[]", 30)
    _bcrypt.lib.encode_base64(output, salt, len(salt))

    return (
        b"$" + prefix + b"$" + ("%2.2u" % rounds).encode("ascii") + b"$" +
        _bcrypt.ffi.string(output)
    )

一方の py-bcrypt の bcrypt/__init__.py にある gensalt() の実装を見てみると、log_rounds だけです。

def gensalt(log_rounds = 12):
    """Generate a random text salt for use with hashpw(). "log_rounds"
    defines the complexity of the hashing, increasing the cost as
    2**log_rounds."""
    return encode_salt(os.urandom(16), min(max(log_rounds, 4), 31))

以上から、bcrypt から py-bcrypt に移行するときは prefix をなくして rounds を log_rounds に直せば OK です。逆の場合は 2a を指定したいときだけ prefix = b'2a' を与えれば良いです。py-bcrypt は 2a 固定になっていました。

hashpw() と checkpw()

hashpw() と checkpw() は特に変更することなく、正常に使えているようです。