LoginSignup
14
6

More than 1 year has passed since last update.

はじめに

NimbleTOTPを楽しんでみます。

What is NimbleTOTP ?

NimbleTOTP is a tiny library for Two-factor authentication (2FA) that allows developers to implement Time-Based One-Time Passwords (TOTP) for their applications.

二段階認証のアレです。(アレって何ですか? :relaxed:

二段階認証ってそもそもなんだっけ? どうしてサーバ側とスマートフォンアプリのワンタイムパスワードが一致するのかが気になりました。
QRコードをスマートフォンアプリ(Google AuthenticatorやMicrosoft Authenticatorなど)で読み込むと30秒に一回パスワードが更新されて、ログインのときに入力をするアレです。
使い方はわかるけど仕組みはどうなっているのだ? とおもったわけです。

に書いてあります。

詳しいことは他の方の説明に譲るとして、仕組みは割と単純です(※私は上記RFCをちゃんと読んでいません!)。
サーバ側で作ったシークレットがあのQRコードに埋め込まれていて、スマートフォンアプリはそれを端末に保存します。
シークレットと時間があればパスワードは導出できるアルゴリズムとなっています。
別の言い方をすると、シークレットさえわかれば時間の変化ととともに刻々と変化するパスワードがつくれるわけです。
サーバ側では、ユーザアカウントとシークレットを紐付けています。

totop.png

usagelib/nimble_totp.exをみるとなんとなくわかるとおもいます。
細かな部分を完全には理解していませんが、シークレットと時間をパラメータにして、パスワードが変化することを読み取れるとおもいます。
ライブラリを使う分には不自由はありません。
以外と短くてびっくりします。

excalidraw

ちょっと横道にそれます。

さきほどの手書き風の図は、excalidrawで描きました。
最近、私がお気に入りのツールです。

で知りました。
凄腕の開発者がホワイトボードになぐり書きした感じが良いです。

使ってみる

phx.gen.authと組み合わせて認証処理を組み込みましたーーーーッ! が正攻法だとおもいます。
私はへそ曲がりなので、スマートフォンのアプリのような刻々とパスワードが変化するものをLiveViewで作ってみました。

できあがったものの様子はこちらです。

比較用に、同じシークレットを読み込ませたMicrosoft Authenticatorのワンタイムパスワードを画面の下側で動かしています。
(手ブレしていてグラグラしてて、見ていると気持ち悪くなりますね:money_mouth:

Microsoft Authenticatorはandroidの録画機能で撮影してみましたが真っ黒になって動画に入っていませんでした。それで原始的な方法で別のカメラで撮影しました。

シークレットは、RubyのGem、 mdp/rotpサンプルで使われているJBSWY3DPEHPK3PXPを使いました。

Phoenixアプリのnew

プロジェクトを作ります。

mix phx.new hello

mix.exsを編集します。

mix.exs
-      {:plug_cowboy, "~> 2.5"}
+      {:plug_cowboy, "~> 2.5"},
+      {:nimble_totp, "~> 0.1.0"}

ソースコードを作ります。

lib/hello_web/router.ex
     pipe_through :browser
 
     get "/", PageController, :index
+    live "/totp", TotpLive
   end
lib/hello_web/live/totop_live.ex
defmodule HelloWeb.TotpLive do
  use HelloWeb, :live_view

  def render(assigns) do
    ~H"""
    <h1><%= format_password(@password) %></h1>
    <%= @remain %> s
    """
  end

  def mount(_params, _session, socket) do
    if connected?(socket), do: Process.send_after(self(), :update, 1000)

    {:ok, update_socket(socket)}
  end

  def handle_info(:update, socket) do
    Process.send_after(self(), :update, 1000)
    {:noreply, update_socket(socket)}
  end

  defp update_socket(socket) do
    now_one_time_password = Hello.DummyTotp.verification_code()

    index =
      0..30
      |> Enum.map(&Hello.DummyTotp.verification_code/1)
      |> Enum.find_index(&(&1 != now_one_time_password))

    socket
    |> assign(:password, now_one_time_password)
    |> assign(:remain, index)
  end

  defp format_password(password) do
    String.slice(password, 0..2) <> " " <> String.slice(password, 3..5)
  end
end

lib/hello/dummy_totp.ex
defmodule Hello.DummyTotp do
  @secret Base.decode32!("JBSWY3DPEHPK3PXP")

  def verification_code(offset \\ 0) do
    NimbleTOTP.verification_code(@secret, time: System.os_time(:second) + offset)
  end
end

あとは以下のコマンドです。

cd hello
mix setup
mix phx.server

迷わず動かしてください。
残り3秒になったら、この動画の最後のほうをご覧になり、ご唱和ください。

「1、2、3ダー!」誕生までの軌跡は、こちらに詳しくまとめられています。

おわりに

NimbleTOTPを楽しみました。

$\huge{1、2、3ダー!}$

14
6
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
14
6