※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。


PythonでGoogleの2段階認証を実装する方法解説

python3 セキュリティ 2段階認証 自動化 python

投稿日:2019年10月31日

このエントリーをはてなブックマークに追加
最近良く聞く2段階認証ですが、Pythonではライブラリを使って簡単に実装ができちゃいます!この記事ではその方法について解説しています。

はじめに

この記事について

この記事では2段階認証についての簡単な説明と、Pythonを使ったGoogleの2段階認証の実装方法について解説します。

参考書籍

2段階認証とは

WebサイトやスマホアプリのログインにはID(メールアドレス)パスワードがよくつかわれます。2段階認証はそれに加えてユーザーに追加のSMSや指紋の認証作業をしてもらうことを言います。これはID・パスワードの認証が突破され、なりすましや情報漏洩されることを防ぐことを目的としています。

例えば、ログインにユーザーが使用するスマホを使った2段階認証が必須になっていれば、悪意のある第三者がIDとパスワードの組を見つけたとしても、そのスマホをもっていないのでログインできないというわけです。

2段階認証によく使われる方法

  • 携帯電話番号あてのSMS
  • 定期的・またはアルゴリズムでコードが変化するようなスマホアプリ(Google Authenticatorなど)
  • 指紋や瞳の虹彩などを使った生体認証

2段階認証の注意点

2段階認証は特定の物質に依存しがちになるため、実装の際にはいくつか注意しておくべき点があります。

  1. 上で説明したように、2段階認証ではよくスマートホンが使われます。スマホを使った実装をする場合、スマホをなくしたりした場合や機種変更をする場合ログインできなくなってしまいます。このような場合に備えて、万が一スマートホンがなくても再登録の方法を考えておく必要があります。
  2. 生体認証で実装する場合、身体に障害がある方が利用できない可能性を考慮しなくてはいけません。

実践(PyOTPの使い方)

ここで実際にPythonでGoogleの2段階認証を実装してみましょう!

環境

  • Ubuntu 18.04LTS
  • Python 3.6.7
  • python3-pip 19.1.1

ライブラリのインストール

今回使用するライブラリは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

TOTPは時間を基準に定期的に値が切り替わります。

サーバー側ではあらかじめユーザーごとに1意になる値を設定しておき、その値を基準に一定時間ごとに値が更新されていきます。

PyOTPの場合、サーバー側で指定する一意の値はTOTP()メソッドを呼ぶときに渡します。この値はbase32の文字列でなければいけない点に注意してください。今回はbase32secret3232という値にします(特に意味はなし)。それぞれの時間ごとの値はnow()メソッドで確認することができます。単純にユーザーから送られた値が正しいかどうかだけ確かめたい場合には、その値が正しいかどうかをbool型で返すverify()メソッドが実装されているのでそちらを使うと良いでしょう。

TOTPを使う例
import pyotp

totp = pyotp.TOTP('base32secret3232') # サーバー側であらかじめ指定する一意の値
totp.now() # => '492039'                # nowメソッドで値の確認


totp.verify('492039')                        # => True

HOTP

HOTPはカウンターベースで値が更新されていきます。サーバー側ではあらかじめユーザーごとに1意になる値を設定しておき、その値を基準にカウンタの値でパスワードが決定されます。カウンタはサーバー側とユーザー側で同じ値を記録しておき、ログイン指向ごとにそのカウンタを増加させることで値の更新を行うのがベストだと思います。

PyOTPの場合、サーバー側で指定する一意の値ははじめにHOTP()メソッドを呼ぶときに指定します。この値はbase32の文字列でなければいけない点に注意してください。上と同じbase32secret3232という値にします(やはり意味はなし)。

それぞれのカウンタでどのようなパスワードの値になるのかはat()メソッドで確認することができます。TOTPと同じ用にverify()メソッドも実装されています。

HOTP
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ではこの乱数を生成する以下のメソッドが提供されています。

random_base32()

このメソッドはbase32を使った文字列を生成します。Google Authenticatorなどのアプリをユーザーに使わせたい場合にはこちらを使うことになるでしょう。

import pyotp
pyotp.random_base32()

フロント側の実装はどうしよう??

この2段階認証ではフロント側とサーバー側で同じアルゴリズムでパスワードを生成することで認証機能を成り立たせています。

「つまりフロントでもこれと同じアルゴリズムのパスワード生成を実装しないといけない??」

と思う方が多いでしょう。

完結に言えばそうなのですが、もっと簡単な方法があります。それがGoogle Authenticatorというスマホアプリを使う方法です。このアプリはそれぞれのパスワードを管理し、そのOneTimeパスワードを常に確認することができるのです。

サーバー側はこのユーザー側に適切なURLを送るだけで後はアプリが全て管理してくれます。便利なことにGoogle Authenticator用のURLもPyOTPのメソッドprovisioning_uri()で簡単に生成できます。

provisioning_uri()

このメソッドで簡単にGoogleAuthenticator用のURLが生成できます。TOTPHOTPはアルゴリズムが違うためこの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'

発展

自分でQRコードを作成してPyOTPを動かして見よう!!

ここでは実践に近い練習をしてみましょう。スマホにGoogleAuthenticatorがインストールされていない方はインストールしておいてください。

ここでは下の様な手順でPyTOPの認証を試します。

  1. ユーザーに表示するQRコードを生成
  2. QRコードをスマホで読み取り
  3. アプリに表示される値とコードに表示される値を比較して確認

まずは必要なライブラリをインストールします。

ターミナル
$ pip install qrcode==6.1 Pillow==6.1.0 pyotp==2.3.0
generate_qr.py
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
このエントリーをはてなブックマークに追加


関連記事

記事へのコメント
1:名無しさん
2020年3月28日23:20

二段階認証って具体的にどこで使うんや??

5:名無しさん
2023年2月26日14:36

自分がやりたいことズバリが記述されていて、大変参考になりました。
ありがとうございました。