From bf205d5e6e7e4464d56773842408541618e0e13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=8A=E6=99=9A=E6=89=93=E8=80=81=E8=99=8E?= Date: Tue, 28 Apr 2026 21:39:09 +0800 Subject: [PATCH 1/2] =?UTF-8?q?[=E6=8A=95=E7=A8=BF]=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=84=9A=E6=9C=AC:=20=E9=AA=8C=E8=AF=81=E5=99=A8=E7=9A=84?= =?UTF-8?q?=E5=AF=86=E9=92=A5=E7=94=9F=E6=88=90=E5=99=A8=20=E2=80=94=20by?= =?UTF-8?q?=20dreamingbob?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scripts/basic/script_moio9udy.py | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 script_library/scripts/basic/script_moio9udy.py diff --git a/script_library/scripts/basic/script_moio9udy.py b/script_library/scripts/basic/script_moio9udy.py new file mode 100644 index 0000000..02f2f16 --- /dev/null +++ b/script_library/scripts/basic/script_moio9udy.py @@ -0,0 +1,159 @@ +import base64 +import secrets +import hmac +import hashlib +import struct +import time +import urllib.parse + + +def generate_base32_secret(byte_length=20): + random_bytes = secrets.token_bytes(byte_length) + secret = base64.b32encode(random_bytes).decode("utf-8") + return secret.rstrip("=") + + +def build_otpauth_uri(secret, account_name, issuer_name): + label = issuer_name + ":" + account_name + + params = { + "secret": secret, + "issuer": issuer_name, + "algorithm": "SHA1", + "digits": "6", + "period": "30" + } + + uri = "otpauth://totp/" + urllib.parse.quote(label) + uri += "?" + urllib.parse.urlencode(params) + return uri + + +def base32_decode_no_padding(secret): + secret = secret.strip().replace(" ", "").upper() + missing_padding = len(secret) % 8 + + if missing_padding: + secret += "=" * (8 - missing_padding) + + return base64.b32decode(secret) + + +def generate_totp(secret, interval=30, digits=6): + key = base32_decode_no_padding(secret) + counter = int(time.time() // interval) + counter_bytes = struct.pack(">Q", counter) + + hmac_hash = hmac.new( + key, + counter_bytes, + hashlib.sha1 + ).digest() + + offset = hmac_hash[-1] & 0x0F + + code_int = struct.unpack(">I", hmac_hash[offset:offset + 4])[0] + code_int = code_int & 0x7FFFFFFF + + code = code_int % (10 ** digits) + return str(code).zfill(digits) + + +def verify_totp(secret, user_code, interval=30, digits=6, window=1): + current_time = int(time.time()) + key = base32_decode_no_padding(secret) + + for offset in range(-window, window + 1): + test_time = current_time + offset * interval + counter = int(test_time // interval) + counter_bytes = struct.pack(">Q", counter) + + hmac_hash = hmac.new( + key, + counter_bytes, + hashlib.sha1 + ).digest() + + dynamic_offset = hmac_hash[-1] & 0x0F + + code_int = struct.unpack(">I", hmac_hash[dynamic_offset:dynamic_offset + 4])[0] + code_int = code_int & 0x7FFFFFFF + + expected_code = str(code_int % (10 ** digits)).zfill(digits) + + if expected_code == user_code: + return True + + return False + + +def main(): + print("======================================") + print(" Google Authenticator 密钥生成器") + print(" 手机 Python IDE 纯 Python 版") + print("======================================") + + issuer_name = input("请输入服务名称,例如 MyApp:").strip() + account_name = input("请输入账号名称,例如 ding@example.com:").strip() + + if issuer_name == "": + issuer_name = "MyApp" + + if account_name == "": + account_name = "user@example.com" + + secret = generate_base32_secret() + uri = build_otpauth_uri(secret, account_name, issuer_name) + + print("") + print("生成完成:") + print("--------------------------------------") + print("服务名称:", issuer_name) + print("账号名称:", account_name) + print("TOTP 密钥:") + print(secret) + print("--------------------------------------") + print("Google Authenticator 链接:") + print(uri) + print("--------------------------------------") + + print("") + print("使用方法:") + print("1. 打开 Google Authenticator") + print("2. 选择添加代码") + print("3. 选择输入设置密钥") + print("4. 账号名称填写上面的账号名称") + print("5. 密钥填写上面的 TOTP 密钥") + print("6. 密钥类型选择基于时间") + + current_code = generate_totp(secret) + print("") + print("当前这台设备根据密钥算出的 6 位验证码是:") + print(current_code) + print("这个数字应该和 Google Authenticator 里显示的数字一致。") + + while True: + print("") + user_code = input("输入 Google Authenticator 当前显示的 6 位验证码测试,直接回车退出:").strip() + + if user_code == "": + print("已退出。请妥善保存密钥。") + break + + if not user_code.isdigit() or len(user_code) != 6: + print("请输入 6 位数字。") + continue + + if verify_totp(secret, user_code): + print("验证成功,这个密钥可以正常使用。") + break + else: + print("验证失败。请检查手机时间是否准确,或等待下一个 30 秒验证码。") + + print("") + print("重要提醒:") + print("TOTP 密钥不要发给别人。") + print("谁拿到密钥,谁就能生成同样的验证码。") + + +main() \ No newline at end of file From 15c078473fa4011a44429d5b64c24c0df62fbfa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=8A=E6=99=9A=E6=89=93=E8=80=81=E8=99=8E?= Date: Tue, 28 Apr 2026 21:39:12 +0800 Subject: [PATCH 2/2] =?UTF-8?q?[=E6=8A=95=E7=A8=BF]=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20index.json:=20=E6=B7=BB=E5=8A=A0=20=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E5=AF=86=E9=92=A5=E7=94=9F=E6=88=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script_library/index.json | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/script_library/index.json b/script_library/index.json index ba30b5f..4ac0b82 100644 --- a/script_library/index.json +++ b/script_library/index.json @@ -1,7 +1,7 @@ { "format_version": 1, - "data_version": 106, - "updated": "2026-04-27T18:20:25.395Z", + "data_version": 107, + "updated": "2026-04-28T13:39:10.974Z", "announcement": null, "categories": [ { @@ -1793,6 +1793,29 @@ "updated": null, "status": "active", "lines": 559 + }, + { + "id": "script_moio9udy", + "name": "验证器的密钥生成器", + "name_en": "验证器的密钥生成器", + "desc": "生成在谷歌验证器等验证软件的密钥", + "desc_en": "生成在谷歌验证器等验证软件的密钥", + "category": "basic", + "file": "scripts/basic/script_moio9udy.py", + "thumbnail": null, + "version": 1, + "file_type": "py", + "author": "dreamingbob", + "author_en": "dreamingbob", + "tags": [ + "community" + ], + "requires": [], + "min_app_version": "1.5.0", + "added": "2026-04-28", + "updated": null, + "status": "active", + "lines": 159 } ] }