LoginSignup
54
49

More than 5 years have passed since last update.

Rubyで2段階認証(2FA)用のQRコードを生成する流れ

Last updated at Posted at 2014-10-24

2段階認証について調べる機会があったのでまとめてみます。

2段階認証

2段階認証(Two Factor Authentication) とはWikipediaによると、「異なる2つのコンポーネントを組み合わせにより、ユーザーの明確な識別を提供する認証」とあります。(以下、2段階認証を2FAと書きます)

会社のVPNにアクセスするためにパスワードと認証用のUSBスティックを使う、というのも2FAになります。
Webサービスの場合はユーザが設定したパスワードに加えて、ワンタイムパスワードを用いる方式が主流となっています。

このワンタイムパスワードの生成にはRFCで仕様が決められているHOTP(HMAC-based One-time Password Algorithm)TOTP(Time-based One-time Password Algorithm)が用いられます。
よく利用されているのは時間ベースのTOTPなので以下はTOTPにフォーカスしていきたいと思います。

ざっくりとパスワード生成のルールを下記に列挙します。(詳細はRFCをご参照ください)

  • クライアントとサーバーは同じsecret(秘密鍵)を共有する
  • クライアントとサーバーは同じtime-step(鍵更新のインターバルとなる秒)の値を使う
  • クライアントごとにユニークなsecretを持つ
  • secretはランダムなアルゴリズムから生成された方がよい

TOTPはこれらのルールとUnixtimeを使ってパスワードを生成することになります。

Rubyで実装

rotp (Ruby One Time Password Library) というgemが上で述べた仕様を実装してくれているのでこれを利用します。またGoogle Authenticatorで読み込めるようにQRコードを生成するのでrqrcodeも利用します。

サンプルコード

require 'rotp'
require 'rqrcode'
require 'rqrcode_png'

secret = ROTP::Base32.random_base32
totp   = ROTP::TOTP.new(secret)
uri    = totp.provisioning_uri("qiita.com/mitz")

qr     = RQRCode::QRCode.new(uri, size: 12)
png    = qr.to_img
png.save("2fa.png")

処理フローは下記です(簡単ですね!)
1. パスワードを生成
2. TOTPオブジェクトを生成
3. uri形式に
4. 生成したuriをQRコード画像にして保存

実際に生成されたQRコードはこちらです。お手元にGoogle Authenticatorアプリ(もしくは他の2FA認証用アプリ)がある人は読み込めるか試してみてください。

2fa.png

rotpでできること

パスワード生成用のメソッドが用意されています。

ROTP::Base32.random_base32     # デフォルトlengthは16文字
ROTP::Base32.random_base32(32) # length指定できます

TOTPまわり

totp = ROTP::TOTP.new("secret") 
# 現在のUnixtimeからワンタイムパスワード生成
totp.now
# ワンタイムパスワードの検証
totp.verify("012345")
# KeyUriFormatの出力
# FYI. https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
uri = totp.provisioning_uri("LABEL")

# issuerの付与
totp_with_issuer = ROTP::TOTP.new("secret", :issuer => "multipleDB")

# パスワード更新のインターバル指定(デフォルト30秒)
totp_with_interval = ROTP::TOTP.new("secret", :interval => 60)

# clock driftを考慮したパスワード検証
# ネットワーク遅延などによるGAPを考慮してインターバル+αを指定できる
now = Time.utc(2014,10,24).to_i
totp.verify_with_drift("567890", 30, now)  # この指定だと30秒古いパスワードを通すようになる

HOTPも扱えます


hotp = ROTP::HOTP.new("secret")
hotp.at(1234) # HOTPは時間ではなくカウンター値でパスワードを生成します
hotp.verify("509997", 1234) # => true

まとめ

ライブラリを使えば、簡単に2FAを導入することができるのでよりセキュアな認証が必要だと考えているサービスは検討しても良いのではないでしょうか!

追記

関連するRFCをブログに書いたのでリンク貼っておきます。
TOTP: Time-Based One-Time Password Algorighm のRFC読んだのでメモ - モノノフ日記

54
49
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
54
49