はじめに
今回はpyotp
とqrcode
を使ってOTP(ワンタイムパスコード)を使えるようにしていきます。
実行環境
- Python 3.11
- Windows 10
使用ライブラリ
- pyotp
- qrcode
ライブラリのインストール
今回のプログラムではpyotp
とqrcode
の2つの外部ライブラリを使用します。以下のコマンドでインストールしましょう。どちらのコマンドを使っても大丈夫です。
pip install pyotp
pip install qrcode
又は
pip install pyotp qrcode
実装
以下のコードを先程作成した.pyファイルにコピペしてください。
実行するとターミナルにシークレットキーが表示されます。
表示されたシークレットキーをOTPアプリに入力するか、生成されるotp_ar.png
のQRコードを読み取って登録!
OTPアプリに表示されたワンタイムパスコードを入力すると検証結果が表示されます。
プログラムをもう一度実行すると新しいシークレットキーが生成され、前回生成したシークレットキーは無効になります。
任意でコード内のexample@domain.com
とYourAppName
は変更してください。
import pyotp
import qrcode
# ユーザーごとのシークレットキーを生成
secret = pyotp.random_base32()
print(f"Your secret key: {secret}")
# ワンタイムパスコードの生成
totp = pyotp.TOTP(secret)
# QRコード生成用のURL
url = totp.provisioning_uri(name="example@domain.com", issuer_name="YourAppName")
print(f"Provisioning URL: {url}")
# QRコードを生成して保存
qrcode.make(url).save("otp_qr.png")
print("QR code saved as otp_qr.png")
# ユーザー入力と検証
user_input = input("Enter the OTP: ")
if totp.verify(user_input):
print("Login successful!")
else:
print("Invalid OTP. Please try again.")
おわりに
今回はOTPを実装してみました。
こちらの記事がOTPを実装する際の参考になれば嬉しいです。
おまけ
実際に実装するとなると現在のコードでは毎回シークレットキーが生成されてしまうので、実務的には使いにくいです。
例えば、シークレットキーをtxtやjsonに保存し、それを再利用するのをおすすめします。
適当に作ってみたので置いときます。
import pyotp
import qrcode
import os
import tkinter as tk
from PIL import Image, ImageTk
from tkinter import messagebox
import tkinter.simpledialog as simpledialog
import json
security_data_path = "security.json"
if not os.path.exists(security_data_path):
data = {
"OTP_secret_key": None
}
# JSONファイルへの書き込み
with open(security_data_path, 'w', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
# JSONファイルの読み込み
with open(security_data_path, 'r', encoding='utf-8') as file:
security_data = json.load(file)
# シークレットキーの保存先
SECRET_KEY_FILE = "OTP_secret_key.txt"
if not security_data['OTP_secret_key'] == None:
# シークレットキーを取得
secret = security_data['OTP_secret_key']
else :
secret = pyotp.random_base32()
# ワンタイムパスコードの生成
totp = pyotp.TOTP(secret)
# QRコード生成用のURL
url = totp.provisioning_uri(name="最強セキュリティ", issuer_name="mikan")
# QRコードを生成して保存(初回のみ)
if not security_data['OTP_secret_key'] == secret:
qrcode.make(url).save("otp_qr.png")
path = "otp_qr.png"
# ボタンが押されたときにループを終了するフラグ
loop_running = True
def end_loop():
# os.remove(path) QRコードを削除
global loop_running
loop_running = False
root.destroy() # ウィンドウを閉じる
# メインウィンドウの作成
root = tk.Tk()
root.title("QR Code Display with Text")
root.geometry("500x500") # ウィンドウサイズ
# ×ボタンを押されたときの処理を設定
root.protocol("WM_DELETE_WINDOW", end_loop)
# 画像のリサイズと読み込み
image = Image.open(path)
image = image.resize((300, 300)) # 幅300px、高さ300pxにリサイズ
tk_image = ImageTk.PhotoImage(image)
# Canvasの作成(画像とテキストを配置)
canvas = tk.Canvas(root, width=500, height=400)
canvas.pack()
# テキストを画像の上部に描画(Canvas上部に表示)
canvas.create_text(250, 30, text="以下のQRコードでOTPアプリに\n設定してください。", fill="black", font=("Arial", 20))
# 画像をCanvasに描画(テキストの下に画像を配置)
canvas.create_image(250, 225, image=tk_image) # テキストから少し下に配置
# OKボタンの作成
ok_button = tk.Button(root, text="登録が終わった", command=end_loop)
ok_button.pack(pady=20)
# メインループ
while loop_running:
root.update()
# 新しく生成したシークレットキーをJSONに保存
if security_data['OTP_secret_key'] == None:
security_data['OTP_secret_key'] = secret
with open(security_data_path, 'w', encoding='utf-8') as file:
json.dump(security_data, file, ensure_ascii=False, indent=4)
# ユーザー入力と検証
user_input = simpledialog.askstring('OTP入力', 'ワンタイムパスコードを入力してください:')
if totp.verify(user_input):
messagebox.showinfo("成功", "OTP認証に成功しました。")
else:
messagebox.showinfo("失敗", "OTP認証に失敗しました。")
os._exit(0)
このコードでは初回実行時にシークレットキーが生成されsecurity.json
に保存されます。その後生成されたQRコードを表示しています。
二回目以降からはsecurity.json
に保存されたシークレットキーを使用し検証だけを行います。
それとtkinter使って少し使いやすくしました。
同じシークレットキーを使用する場合にはsecurity.json
を削除しないでください。