投稿日:2019年10月31日
最近良く聞く2段階認証ですが、Pythonではライブラリを使って簡単に実装ができちゃいます!この記事ではその方法について解説しています。
この記事では2段階認証についての簡単な説明と、Pythonを使ったGoogleの2段階認証の実装方法について解説します。
WebサイトやスマホアプリのログインにはID(メールアドレス)とパスワードがよくつかわれます。2段階認証はそれに加えてユーザーに追加のSMSや指紋の認証作業をしてもらうことを言います。これはID・パスワードの認証が突破され、なりすましや情報漏洩されることを防ぐことを目的としています。
例えば、ログインにユーザーが使用するスマホを使った2段階認証が必須になっていれば、悪意のある第三者がIDとパスワードの組を見つけたとしても、そのスマホをもっていないのでログインできないというわけです。
2段階認証は特定の物質に依存しがちになるため、実装の際にはいくつか注意しておくべき点があります。
ここで実際にPythonでGoogleの2段階認証を実装してみましょう!
今回使用するライブラリはPyOTPというライブラリです。
まずはpipでインストールします。
$ pip install pyotp==2.3.0
2段階認証の場合、その都度ユーザーに入力させるパスワードを変える事になります。
その変更パターンはPyOTPでは2パターン実装されています。1つがTOTP(Time-Based One Time Password)で、もう一つがHOTP(HMAC-based One-Time Password)になります。
TOTPは時間を基準に定期的に値が切り替わります。
サーバー側ではあらかじめユーザーごとに1意になる値を設定しておき、その値を基準に一定時間ごとに値が更新されていきます。
PyOTPの場合、サーバー側で指定する一意の値はTOTP()メソッドを呼ぶときに渡します。この値はbase32の文字列でなければいけない点に注意してください。今回はbase32secret3232という値にします(特に意味はなし)。それぞれの時間ごとの値はnow()メソッドで確認することができます。単純にユーザーから送られた値が正しいかどうかだけ確かめたい場合には、その値が正しいかどうかをbool型で返すverify()メソッドが実装されているのでそちらを使うと良いでしょう。
import pyotp
totp = pyotp.TOTP('base32secret3232') # サーバー側であらかじめ指定する一意の値
totp.now() # => '492039' # nowメソッドで値の確認
totp.verify('492039') # => True
HOTPはカウンターベースで値が更新されていきます。サーバー側ではあらかじめユーザーごとに1意になる値を設定しておき、その値を基準にカウンタの値でパスワードが決定されます。カウンタはサーバー側とユーザー側で同じ値を記録しておき、ログイン指向ごとにそのカウンタを増加させることで値の更新を行うのがベストだと思います。
PyOTPの場合、サーバー側で指定する一意の値ははじめにHOTP()メソッドを呼ぶときに指定します。この値はbase32の文字列でなければいけない点に注意してください。上と同じbase32secret3232という値にします(やはり意味はなし)。
それぞれのカウンタでどのようなパスワードの値になるのかはat()メソッドで確認することができます。TOTPと同じ用にverify()メソッドも実装されています。
import pyotp
hotp = pyotp.HOTP('base32secret3232')
hotp.at(0) # => '260182'
hotp.at(1) # => '055283'
hotp.at(1401) # => '316439'
# 値が正しいかどうかを確認
hotp.verify('316439', 1401) # => True
hotp.verify('316439', 1402) # => False
TOTP()メソッドやHOTP()メソッドに渡したり、サーバーからユーザー側に渡す、一意の値というのはどのように決めればよいでしょうか?
自分で頭を悩ませて値を決めてもいいのですが、
便利な事にPyOTPではこの乱数を生成する以下のメソッドが提供されています。
このメソッドはbase32を使った文字列を生成します。Google Authenticatorなどのアプリをユーザーに使わせたい場合にはこちらを使うことになるでしょう。
import pyotp
pyotp.random_base32()
この2段階認証ではフロント側とサーバー側で同じアルゴリズムでパスワードを生成することで認証機能を成り立たせています。
「つまりフロントでもこれと同じアルゴリズムのパスワード生成を実装しないといけない??」
と思う方が多いでしょう。
完結に言えばそうなのですが、もっと簡単な方法があります。それがGoogle Authenticatorというスマホアプリを使う方法です。このアプリはそれぞれのパスワードを管理し、そのOneTimeパスワードを常に確認することができるのです。
サーバー側はこのユーザー側に適切なURLを送るだけで後はアプリが全て管理してくれます。便利なことにGoogle Authenticator用のURLもPyOTPのメソッドprovisioning_uri()で簡単に生成できます。
このメソッドで簡単にGoogleAuthenticator用のURLが生成できます。TOTPとHOTPはアルゴリズムが違うためこのprovisioning_uri()メソッドはtotpとhotpのそれぞれで実装され、それぞれ別の値を返します。name引数はユーザーに入力されたメールアドレスなどを入れて上げると良いでしょう。同じサイトで複数のアカウントを作成している人のためにGoogleAuthenticatorアプリではこの値が目印になります。issuer引数はサイト名、または企業・グループ名を入力しておきましょう。
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name="marsquai@google.com", issuer_name="Secure App")
>>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App'
pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name="marsquai@google.com", issuer_name="Secure App",initial_count=0)
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
ここでは実践に近い練習をしてみましょう。スマホにGoogleAuthenticatorがインストールされていない方はインストールしておいてください。
ここでは下の様な手順でPyTOPの認証を試します。
まずは必要なライブラリをインストールします。
$ pip install qrcode==6.1 Pillow==6.1.0 pyotp==2.3.0
import qrcode
import pyotp
import time
# ユーザーに渡す乱数を作成
random_base32 = pyotp.random_base32()
# uriを作成
uri = pyotp.totp.TOTP(random_base32).provisioning_uri(name="marsquai@google.com",issuer_name="サンプルアプリ")
# QRコードを作成
img = qrcode.make(uri)
img.save('qr_code.png')
# 1000秒間OneTimePasswordを表示
totp = pyotp.TOTP(random_base32)
for i in range(1000):
print(totp.now())
time.sleep(1)
このコードを実行すると、qr_code.pngが生成され、ターミナルには1000秒間認証コードが表示され続けます。QRコードをスマホで読み取ってみましょう。
ターミナルの値と同じ値がちゃんと表示されればOKです。
$ python generate_qr.py
自分がやりたいことズバリが記述されていて、大変参考になりました。
ありがとうございました。
二段階認証って具体的にどこで使うんや??