Nao000のぶろぐ

蝶を追っている少年になりたい

Elixir Bcrypt のパスワード認証がどうやって動いているのか全く分からない

全くわからない

おそらく Bcrypt のアルゴリズムだかを調べる必要がありそうです

以下おまけ

Bcrypt.hash_pwd_salt によって生成されるハッシュ化パスワードは実行するごとに異なる文字列が生成されるのに、Bcrypt.verify_pass で認証可能な仕組みが全くわからない。

iex(2) でパスワード認証を行って true となりますが、iex(9) でも同様に true となります。間にBcrypt.hash_pwd_salt("password") で生成される文字列は毎回異なるにも関わらず。

    /home/nao000dotcom # iex -S mix
    Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]

    Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$XLmj0DluwBk4JEpcIAc4HeJTP.NM3fmAscPHaDJhd2zHAYEv//F/G"
    iex(2)> Bcrypt.verify_pass("password", hpass)
    true
    iex(3)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$6ibm1qg0yIR.3WvyQ08E6uT0xGPNUntipPH/vbiTgJqXmluh6VTi2"
    iex(4)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$8iViLepEinSIsnmvo6W3ne0QbjIrhUupzi0kDf7LEpOae7.Ri1foe"
    iex(5)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$uFt22FVkZuyOKcVeSLVT6Oh166Ib6jrZY7r97wbXCSOn8rHqOInwy"
    iex(6)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$vF/JUeqBxNAf0N/ckBLLueU.GX84HX80DY3Kk0xnjfyq.zJi67nxu"
    iex(7)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$YjMXcb62vtzqaFEfh1rfe.kg3LeBK7IUl.9acaUZONRYBb.33vbia"
    iex(8)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$vTzhVBm9NDfnfDQxiDob2O8hirOUHN986C823nY9HkY49d1YzXWw2"
    iex(9)> Bcrypt.verify_pass("password", hpass)
    true

Bcryptモジュールを見ていくと以下のコードがありますが、この書き方がもう自分には理解できない。

      @doc """
      Verify the password by comparing it with the stored hash.
      """
      def checkpass_nif(password, stored_hash)
      def checkpass_nif(_, _), do: :erlang.nif_error(:not_loaded)

1つ目の checkpass_nifdo: ないけどいいの?という感じでした。試しに別ファイルで似たコードを書くとruntime errorにもならずに実行できました。

    defmodule Sample do
      def checkpass_nif(password, stored_hash)
      def checkpass_nif(_, _), do: "hello world"
    end

実行結果

    /home # iex sample.ex
    Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]

    Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> Sample.checkpass_nif("password", "mock_hashed_password")
    "hello world"
    iex(2)>

おそらく def checkpass_nif(password, stored_hash) 側で認証が行われて、その実態はCプログラムなのかな状態です。depscrypt_elixirc_srccrypt_nif.c のそれらしき関数の bcrypt_checkpass_nifprintf を埋め込んで mix deps.compile してから、iex -S mix を実行して、ctrl + c で中断すると HELLO WORLD の文字が確認できます。だから何だというのか。

      @doc """
      Verify the password by comparing it with the stored hash.
      """
      def checkpass_nif(password, stored_hash)
      def checkpass_nif(_, _), do: :erlang.nif_error(:not_loaded)

    static ERL_NIF_TERM bcrypt_checkpass_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
    {
        char pass[BCRYPT_MAXPASS];
        char goodhash[BCRYPT_HASHSPACE + 1];
        char hash[BCRYPT_HASHSPACE + 1];
        hash[BCRYPT_HASHSPACE] = '';

        // 追加
        printf("HELLO WORLD");
    /home/nao000dotcom # iex -S mix
    Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]

    Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
    iex(1)> hpass = Bcrypt.hash_pwd_salt("password")
    "$2b$12$WaivpjNwNb.P1LlQfewGsOv5pZlrI7JYJpwBu1UyModP.zPppJhnO"
    iex(2)> Bcrypt.verify_pass("password", hpass)
    true
    iex(3)>
    BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
           (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
    HELLO WORLD

    ^C/home/nao000dotcom #

全くわからない