HSP3で二段階認証を作った話

# HSP3で二段階認証を作った話
なんかよくわかんないけどPythonとHSP3を合体させて二段階認証作りました。
Pythonファイルがウィルス誤検知されるので完成品は非公開とさせていただきます。
コードだけ記載しますね。

必須環境
windows
python 3
hsp 3.5
認証アプリがインストール済みのスマホ
## 1.構造

二段階認証システム
├system┐
│　　　└qrcodelib.dll(libqrencodeライブラリを使用しています。)
├main.hsp(本体.exeのコード)
├py1.pyw(秘密鍵生成機)
├py2.pyw(6桁コード生成機)
├seccode.txt(秘密鍵)
├sixcode.txt(6桁コード)
└本体.exe(main.hspをexe化したやつ)

## 2.[main.hsp]の中身
pythonとhsp3を合体させてるやつです。
```hsp:main.hsp
;制作:だいちまる
#uselib "system/qrcodelib.dll"
#func QRcode_encodeString "QRcode_encodeString" str,int,int,int,int
#func QRcode_free "QRcode_free" int

username = sysinfo(1)
usercode = ""
screen 0,640,480
title"二段階認証のやつ"
*inputuser
	color 32,32,32
	pos 20,20
	font"BIZ UDゴシック",26
	mes"二段階認証サンプル"
	font"BIZ UDPゴシック",18
	pos 20,100
	mes"ユーザー名を入力してください。"
	objsize 250,24
	font"BIZ UDPゴシック",16
	objmode 2
	input username,,,12

	pos 20,200
	font"BIZ UDPゴシック",18
	mes"説明\nGoogleとかにある２段階認証をHSPで使えるようにしたくて\nPythonと連携するような形で使えるようにしたものです。\n実質中身はほぼPythonです。\n初めて利用される方は「コード作成」、\nコードを追加済みの方は「二段階認証」を押してください。"

	font"BIZ UDゴシック",14
	pos 400,420
	objsize 100,40
	button"二段階認証",*chk
	pos 520,420
	objsize 100,40
	button"コード生成",*make
	
stop
*make
	if username = "":dialog "ユーザー名を入力してください。":stop
	clrobj
	color 255,255,255:boxf
	exec"py1.exe",16
	wait 30;exe実行時間
	notesel secret
	noteload"seccode.txt"
	noteget seccode,0
	totplink = "otpauth://totp/二段階認証のやつ?secret="+seccode+"&issuer="+username+""
	goto*hyoji
	stop

*hyoji
;QR生成
	// 使用ライブラリ
	// libqrencode
	// http://fukuchi.org/works/qrencode/index.html
	
	// 入手先(Win32バイナリ)
	// qrencode-win32 qrcode-win32-3.1.1.zip を解凍した先のDLLフォルダに入ってます
	// http://code.google.com/p/qrencode-win32/

	// 参考
	// http://fukuchi.org/works/qrencode/manual/qrencode_8h.html
	

	// QRコード化したいURLをいれる
	bun = totplink
	b = 4	// 倍率
	ox = 230	// オフセットX
	oy = 150	// オフセットX
	
	QRcode_encodeString bun, 0, 3, 3, 1
	qrcode = stat
	dupptr res, qrcode, 12, 4
	size = res.1
	dupptr data, res.2, size*size, 2
	
	// QRコードの描画
	color : redraw 0
	i = 0 : x = 0
	repeat size
		x = cnt
		repeat size
			if peek(data,i)&1{
				boxf cnt*b+ox, x*b+oy, cnt*b+b+ox, x*b+b+oy
			}
			i++
		loop
	loop
	redraw 1
	
	// 終わったら解放
	QRcode_free qrcode


font"BIZ UDゴシック",26
pos 30,60
mes"下のコードを認証用アプリで読み取ってください。"


font"BIZ UDゴシック",14
pos 70,90
mes totplink

	font"BIZ UDゴシック",14
	pos 520,420
	objsize 100,40
	button"二段階認証",*chk

	stop
*chk
	ct = 0
	clrobj
	color 255,255,255:boxf
	color 32,32,32
	font"BIZ UDゴシック",26
pos 50,60

mes"下の入力欄に6桁のコードを入力してください。"

color 64,64,64
boxf 225,195,444,272
pos 230,200
	objsize 210,68
	font"BIZ UDゴシック Bold",66
	objmode 2
input usercode,,,6

	font"BIZ UDゴシック",14
	pos 520,420
	objsize 100,40
	button"OK",*codechk

	stop
	
*codechk
	
	exec"py2.exe",16
	pos 20,200
	font"BIZ UDPゴシック",18
	mes"少々お待ちください..."
	wait 30;exe実行時間
	notesel sixnum
	noteload"sixcode.txt"
	noteget sixcode,0

;合ってるか確認
	if usercode = sixcode{goto*okdesu}else{dialog"コードが違います。"}
	stop

*okdesu
	clrobj
	color 255,255,255:boxf
	color 32,32,32
	font"BIZ UDゴシック",26
pos 50,60

mes"認証が完了しました。"
stop
```
## 3.[py1.pyw]の中身
秘密鍵生成機です。
```py:py1.py
# 制作:だいちまる
import hmac, base64, struct, hashlib, time ,random, string

# ランダムな秘密鍵を生成
def randomname(n):
   randlst = [random.choice(string.ascii_letters + string.digits) for i in range(n)]
   return ''.join(randlst)

# 秘密鍵
secret = randomname(10)

# 秘密鍵を2進数(バイナリ)に変換
binary_secret = ''.join(format(ord(c), '08b') for c in secret)

# 5ビットずつ配列に分割
binary_secret_array = [binary_secret[i:i+5] for i in range(0, len(binary_secret), 5)]

# base32に変換
base32alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
base32_secret = ''
for binary in binary_secret_array:
    bin_pad = binary.ljust(5, '0')  # 5ビットに満たない場合は0パディング
    base32_secret += base32alphabet[int(bin_pad, 2)]

# base32でエンコードした秘密鍵を保存

f = open('seccode.txt', 'w')

f.write(base32_secret)

f.close()
```
:::note warn
警告
拡張子はpywにしてください。pyでも動きますがHSPとの組み合わせをしたときにちょっと気持ち悪い動きになってしまうので...
:::
## 4.[py2.pyw]の中身
秘密鍵を6桁コードにするやつです。
```py:py2.py
# 制作:だいちまる
import hmac, base64, struct, hashlib, time


f = open('seccode.txt', 'r')
b = f.read()

secretcodes = b

secret = secretcodes

def get_hotp_token(secret: str, intervals_no: int) -> str:
    # まずシークレットキーをデコード
    key = base64.b32decode(secret, True)
    # PythonのデータをCの構造体に変換
    msg = struct.pack(">Q", intervals_no)
    # ハッシュ値を計算
    h = hmac.new(key, msg, hashlib.sha1).digest()
    o = h[19] & 15
    h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
    return str(h)

def get_totp_token(secret):
    # unix時間を30で割った整数をいれている
    x = get_hotp_token(secret=secret, intervals_no=int(time.time())//30)
    # 6桁に満たない場合は6桁になるまでパディング
    while len(x)!=6:
        x+='0'
    return x

secretk = (get_totp_token(secret))

print (get_totp_token(secret))


f = open('sixcode.txt', 'w')

f.write(secretk)

f.close()
```
## 5.[seccode.txt]の中身
```txt:seccode.txt

```
空です。
py1.pywを実行すると秘密鍵が生成されます。
秘密鍵がないとpy2.pywは動きません。
## 6.[sixcode.txt]の中身
```txt:seccode.txt

```
こっちも空です。
py2.pywを実行するとコードが生成されます。
秘密鍵がないと動きません。
## 7.実行方法
:::note warn
警告
[1.構造]で説明したのと同じ配置でファイルを作ってください。
そうでないと動きません。
:::
libqrencodeのdllのdlもお忘れなく
[HSPでQR生成する僕が参考にしたページ](https://codetter.com/?p=525 "Codetterに飛びます")
[dllのダウンロードページ](https://code.google.com/archive/p/qrencode-win32/downloads "Google Code Archiveへ飛びます")
py1.pywとpy2.pywは一部のセキュリティソフトに誤検知される可能性があるので気をつけてください。

まずはmain.hspをふつうに実行してください。
多分こんな画面が出てくると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3227805/27708517-6e7b-f203-aec6-2b92f7db323f.png)
初回起動時はコード生成ボタンを押してください。
py1によって秘密鍵が生成されます。
そしてその鍵のQRコードが表示されるのでスマホの認証アプリで読み込んでください。
こんなQRコードです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3227805/62c4b771-727b-28cf-1b4c-e49aaf37fc07.png)
これで秘密鍵の登録は終わりです。
スマホ側に6桁のコードが表示されているはずです。
そしたら右下の二段階認証ボタンを押してみましょう！
こんな感じのコード入力欄が出てくると思います！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3227805/fae10ef7-a6a8-4bc9-1fff-0f6aa03a8603.png)
そこにスマホで表示されている6桁のコードを入力してください！
入力し終わったらOKボタンを押してください！

:::note info
やったね！
認証できていればOKです！
:::
たま～に認証できないことがあるのでその時は6桁のコードが変わるまでまつか秘密鍵から作り直してみてください。
## 8.おわりに
今回はじめてQiitaで記事を投稿してみました。
まぁHSP3で二段階認証を作ろうとする変態はなかなかいないとは思いますがもし二段階認証を作りたいならがんがんこのコードをコピーしちゃってください～
以上
だいちまるでした～！
## 9.開発環境
- Windows 11
- HSP 3.6
- VScode 1.76.0
- Python 3.10.0