はじめに
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.
二段階認証のアレです。(アレって何ですか? )
二段階認証ってそもそもなんだっけ? どうしてサーバ側とスマートフォンアプリのワンタイムパスワードが一致するのかが気になりました。
QRコードをスマートフォンアプリ(Google AuthenticatorやMicrosoft Authenticatorなど)で読み込むと30秒に一回パスワードが更新されて、ログインのときに入力をするアレです。
使い方はわかるけど仕組みはどうなっているのだ? とおもったわけです。
に書いてあります。
詳しいことは他の方の説明に譲るとして、仕組みは割と単純です(※私は上記RFCをちゃんと読んでいません!)。
サーバ側で作ったシークレットがあのQRコードに埋め込まれていて、スマートフォンアプリはそれを端末に保存します。
シークレットと時間があればパスワードは導出できるアルゴリズムとなっています。
別の言い方をすると、シークレットさえわかれば時間の変化ととともに刻々と変化するパスワードがつくれるわけです。
サーバ側では、ユーザアカウントとシークレットを紐付けています。
usageとlib/nimble_totp.exをみるとなんとなくわかるとおもいます。
細かな部分を完全には理解していませんが、シークレットと時間をパラメータにして、パスワードが変化することを読み取れるとおもいます。
ライブラリを使う分には不自由はありません。
以外と短くてびっくりします。
excalidraw
ちょっと横道にそれます。
さきほどの手書き風の図は、excalidrawで描きました。
最近、私がお気に入りのツールです。
で知りました。
凄腕の開発者がホワイトボードになぐり書きした感じが良いです。
使ってみる
phx.gen.authと組み合わせて認証処理を組み込みましたーーーーッ! が正攻法だとおもいます。
私はへそ曲がりなので、スマートフォンのアプリのような刻々とパスワードが変化するものをLiveViewで作ってみました。
できあがったものの様子はこちらです。
比較用に、同じシークレットを読み込ませたMicrosoft Authenticatorのワンタイムパスワードを画面の下側で動かしています。
(手ブレしていてグラグラしてて、見ていると気持ち悪くなりますね)
Microsoft Authenticatorはandroidの録画機能で撮影してみましたが真っ黒になって動画に入っていませんでした。それで原始的な方法で別のカメラで撮影しました。
シークレットは、RubyのGem、 mdp/rotpのサンプルで使われているJBSWY3DPEHPK3PXP
を使いました。
Phoenixアプリのnew
プロジェクトを作ります。
mix phx.new hello
mix.exs
を編集します。
- {:plug_cowboy, "~> 2.5"}
+ {:plug_cowboy, "~> 2.5"},
+ {:nimble_totp, "~> 0.1.0"}
ソースコードを作ります。
pipe_through :browser
get "/", PageController, :index
+ live "/totp", TotpLive
end
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
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ダー!}$