9 Commits
Author SHA1 Message Date
Nighthawk 0153a420ac Update bnet_auth_tool.py
Forgot `import os` like a dud.
2025-01-21 22:41:23 -05:00
Nighthawk 99203b9bd7 Update bnet_auth_tool.py
Title Update
2025-01-21 22:40:09 -05:00
Nighthawk f4427427a8 Code update.
Refactors some of the code, making it cleaner and more concise. 

Added an offline method of reconstructing TOTP keys and QR code provided the user has the required information.
2025-01-21 22:33:20 -05:00
Nighthawk 9f5bf24e19 Update README.md 2024-12-22 19:17:00 -05:00
Nighthawk 352c195e4d Update README.md 2024-11-29 19:47:17 -05:00
Nighthawk e2c70ecdad Update README.md 2024-11-29 19:46:07 -05:00
Nighthawk e9cfc2a207 Update README.md 2024-11-29 19:45:25 -05:00
Nighthawk 76a4becb30 Update README.md 2024-11-29 19:44:13 -05:00
Nighthawk 1aa4eae45d Create requirements.txt 2024-11-29 19:43:48 -05:00
3 changed files with 207 additions and 92 deletions
+9 -2
View File
@@ -7,6 +7,7 @@ A Python-based tool for managing Battle.net authenticators. This tool allows you
- Attach a new Battle.net authenticator to your account. - Attach a new Battle.net authenticator to your account.
- Retrieve existing device secrets using serial and restore codes. - Retrieve existing device secrets using serial and restore codes.
- Generate TOTP URLs and QR codes for use with TOTP-compatible authenticator apps. - Generate TOTP URLs and QR codes for use with TOTP-compatible authenticator apps.
- Support for both US/EU accounts. - TW/CN accounts are still unknown.
## Requirements ## Requirements
@@ -18,14 +19,20 @@ A Python-based tool for managing Battle.net authenticators. This tool allows you
## Installation ## Installation
Easiest Method?
Use the Release build.
https://github.com/Nighthawk42/bnet_auth_tool/releases/
Manual Method?
1. Clone the repository: 1. Clone the repository:
```bash ```bash
git clone https://github.com/Nighthawk42/bnet-authenticator-tool.git git clone https://github.com/Nighthawk42/bnet-authenticator-tool.git
cd battlenet-authenticator-tool cd bnet-authenticator-tool
2. Run the script: 2. Run the script:
```bash ```bash
pip install -r requirements.txt
py bnet_auth_tool.py` py bnet_auth_tool.py`
4. Follow the instructions from the console window. 4. Follow the instructions from the console window.
+195 -90
View File
@@ -4,7 +4,9 @@ import binascii
import requests import requests
from pathlib import Path from pathlib import Path
import sys import sys
import os
import qrcode import qrcode
from typing import Any, Dict
print(r""" print(r"""
____ _ _ _ _ ____ _ _ _ _
@@ -29,7 +31,14 @@ print(r"""
""") """)
print("Battle.net Authenticator Tool - by Nighthawk42") print("Battle.net Authenticator Tool - by Nighthawk42 - Version 1.1 (01/21/2025)")
class Title:
"""Console/Window Title."""
if sys.platform == "win32":
os.system('title Battle.net Authenticator Tool')
else:
sys.stdout.write("\x1b]2;Battle.net Authenticator Tool\x07")
class Config: class Config:
"""Configuration for Battle.net Authenticator API.""" """Configuration for Battle.net Authenticator API."""
@@ -37,7 +46,6 @@ class Config:
SSO_URL = "https://oauth.battle.net/oauth/sso" SSO_URL = "https://oauth.battle.net/oauth/sso"
CLIENT_ID = "baedda12fe054e4abdfc3ad7bdea970a" CLIENT_ID = "baedda12fe054e4abdfc3ad7bdea970a"
class BattleNetAuthenticator: class BattleNetAuthenticator:
""" """
Handles Battle.net Authenticator operations, including attaching an authenticator, Handles Battle.net Authenticator operations, including attaching an authenticator,
@@ -48,34 +56,121 @@ class BattleNetAuthenticator:
self.bearer_token = None self.bearer_token = None
@staticmethod @staticmethod
def save_plain_json(filename, data): def save_plain_json(filename: str, data: Dict[str, Any]) -> None:
""" """
Saves data to a JSON file, prompting to overwrite if the file already exists. Saves data to a JSON file, prompting to overwrite if the file already exists.
""" """
if Path(filename).exists(): if Path(filename).exists():
overwrite = input(f"{filename} already exists. Do you want to overwrite it? (y/n): ").strip().lower() while True:
overwrite = input(f"{filename} already exists. Do you want to overwrite it? (y/n): ").strip().lower()
if overwrite in {"y", "n"}:
break
print("Invalid input. Please enter 'y' or 'n'.")
if overwrite != "y": if overwrite != "y":
print("Data not saved.") print("Data not saved.")
return return
with open(filename, "w") as f: try:
json.dump(data, f, indent=4) with open(filename, "w") as f:
print(f"Data saved to {filename}.\n") json.dump(data, f, indent=4)
print("IMPORTANT: Ensure you securely back up this file and its contents.") print(f"Data saved to {filename}.")
print("IMPORTANT: Ensure you securely back up this file and its contents.")
except IOError as e:
print(f"Failed to save data to {filename}: {e}")
@staticmethod @staticmethod
def generate_qr_code(totp_url, filename): def load_json(filename: str) -> Dict[str, Any]:
""" """
Generates a QR code for the given TOTP URL and saves it as an image. Loads data from a JSON file.
""" """
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4) try:
with open(filename, "r") as f:
data = json.load(f)
return data
except IOError as e:
print(f"Failed to load data from {filename}: {e}")
return {}
@staticmethod
def convert_secret_to_base32(secret: str) -> str:
"""
Converts a device secret to base32 encoding.
"""
try:
hex_secret = binascii.unhexlify(secret)
return base64.b32encode(hex_secret).decode("utf-8").replace("=", "")
except (binascii.Error, TypeError) as e:
raise Exception(f"Failed to convert secret: {e}")
@staticmethod
def generate_qr_code(totp_url: str, filename: str) -> None:
"""
Generates a QR code for the TOTP URL and saves it as an image file.
"""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(totp_url) qr.add_data(totp_url)
qr.make(fit=True) qr.make(fit=True)
img = qr.make_image(fill="black", back_color="white") img = qr.make_image(fill='black', back_color='white')
qr_filename = f"{filename}.png" img.save(f"{filename}.png")
img.save(qr_filename) print(f"QR code saved as {filename}.png")
print(f"QR Code saved to {qr_filename}. You can scan this code using your authenticator app.")
def reconstruct_totp_from_json(self) -> None:
"""
Reconstructs the TOTP key and QR code from a JSON file or prompts the user for information.
"""
json_files = list(Path('.').glob('*.json'))
if len(json_files) == 1:
filename = json_files[0]
elif len(json_files) > 1:
print("Multiple JSON files found:")
for i, file in enumerate(json_files, 1):
print(f"{i}. {file}")
choice = input("Enter the number of the file to use, or type 'manual' to enter information manually: ").strip()
if choice.lower() == 'manual':
filename = None
else:
try:
index = int(choice) - 1
filename = json_files[index]
except (ValueError, IndexError):
print("Invalid choice.")
return
else:
filename = None
if filename:
data = self.load_json(filename)
if not data:
print("Failed to load JSON file. Prompting for manual input.")
data = {}
else:
data = {}
serial = data.get("serial") or input("Enter Serial: ").strip()
restore_code = data.get("restoreCode") or input("Enter Restore Code: ").strip()
device_secret = data.get("deviceSecret") or input("Enter Device Secret: ").strip()
if not serial or not restore_code or not device_secret:
print("Incomplete data provided.")
return
base32_secret = self.convert_secret_to_base32(device_secret)
totp_url = f"otpauth://totp/Battle.net?secret={base32_secret}&digits=8"
print(f"TOTP URL: {totp_url}")
print("\nImportant: When importing the key, use these settings:")
print(" - Digits: 8")
print(" - Algorithm: SHA1")
print(" - Timeout: 30 seconds")
self.generate_qr_code(totp_url, f"reconstructed_{serial}")
input("\nPress any key to return to the main menu...")
def get_bearer_token(self, session_token): def get_bearer_token(self, session_token):
""" """
@@ -131,18 +226,6 @@ class BattleNetAuthenticator:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
raise Exception(f"Failed to retrieve device secret: {e}") raise Exception(f"Failed to retrieve device secret: {e}")
@staticmethod
def convert_secret_to_base32(device_secret):
"""
Converts the device secret from hex to Base32 format for TOTP.
"""
try:
hex_secret = binascii.unhexlify(device_secret)
return base64.b32encode(hex_secret).decode("utf-8").replace("=", "")
except (binascii.Error, TypeError) as e:
raise Exception(f"Failed to convert secret: {e}")
def show_session_token_instructions(): def show_session_token_instructions():
""" """
Displays instructions for retrieving the Battle.net Session Token. Displays instructions for retrieving the Battle.net Session Token.
@@ -157,7 +240,6 @@ def show_session_token_instructions():
print(" Example: US-abcdef12345678 or EU-12345678abcdef") print(" Example: US-abcdef12345678 or EU-12345678abcdef")
print("5. Copy the Session Token and paste it into this tool when prompted.\n") print("5. Copy the Session Token and paste it into this tool when prompted.\n")
def graceful_exit(): def graceful_exit():
""" """
Gracefully exits the program with a backup reminder. Gracefully exits the program with a backup reminder.
@@ -165,95 +247,118 @@ def graceful_exit():
print("\nExiting the program. Ensure you have securely backed up your data.") print("\nExiting the program. Ensure you have securely backed up your data.")
sys.exit(0) sys.exit(0)
def interactive_cli(): def interactive_cli():
""" """
Main interactive CLI for the Battle.net Authenticator Tool. Main interactive CLI for the Battle.net Authenticator Tool.
""" """
authenticator = BattleNetAuthenticator() authenticator = BattleNetAuthenticator()
show_session_token_instructions() while True:
session_token = input("Enter your Session Token (or type 'exit' to quit): ").strip()
if session_token.lower() == "exit":
graceful_exit()
if not session_token:
print("Session Token is required!")
return
try:
print("Fetching Bearer Token...")
bearer_token = authenticator.get_bearer_token(session_token)
print(f"Bearer Token: {bearer_token}")
print("\nChoose an action:") print("\nChoose an action:")
print("1. Attach a new authenticator") print("1. Attach a new authenticator")
print("2. Retrieve existing device secret") print("2. Retrieve existing device secret")
print("3. Exit") print("3. Reconstruct TOTP from JSON")
choice = input("Enter your choice (1/2/3): ").strip() print("4. Exit")
choice = input("Enter your choice (1/2/3/4): ").strip()
if choice == "1": if choice == "1":
print("Attaching Authenticator...") show_session_token_instructions()
device_info = authenticator.attach_authenticator()
serial = device_info["serial"]
restore_code = device_info["restoreCode"]
device_secret = device_info["deviceSecret"]
print(f"Serial: {serial}") session_token = input("Enter your Session Token (or type 'exit' to quit): ").strip()
print(f"Restore Code: {restore_code}") if session_token.lower() == "exit":
graceful_exit()
print("Generating TOTP URL...") if not session_token:
base32_secret = authenticator.convert_secret_to_base32(device_secret) print("Session Token is required!")
totp_url = f"otpauth://totp/Battle.net?secret={base32_secret}&digits=8" continue
print(f"TOTP URL: {totp_url}")
print("\nImportant: When importing the key, use these settings:")
print(" - Digits: 8")
print(" - Algorithm: SHA1")
print(" - Timeout: 30 seconds\n")
# Save data and generate QR code try:
filename = f"authenticator_{serial}" print("Fetching Bearer Token...")
authenticator.save_plain_json(f"{filename}.json", device_info) bearer_token = authenticator.get_bearer_token(session_token)
authenticator.generate_qr_code(totp_url, filename) print(f"Bearer Token: {bearer_token}")
print("Attaching Authenticator...")
device_info = authenticator.attach_authenticator()
serial = device_info["serial"]
restore_code = device_info["restoreCode"]
device_secret = device_info["deviceSecret"]
print(f"Serial: {serial}")
print(f"Restore Code: {restore_code}")
print("Generating TOTP URL...")
base32_secret = authenticator.convert_secret_to_base32(device_secret)
totp_url = f"otpauth://totp/Battle.net?secret={base32_secret}&digits=8"
print(f"TOTP URL: {totp_url}")
print("\nImportant: When importing the key, use these settings:")
print(" - Digits: 8")
print(" - Algorithm: SHA1")
print(" - Timeout: 30 seconds")
# Save data and generate QR code
filename = f"authenticator_{serial}"
authenticator.save_plain_json(f"{filename}.json", device_info)
authenticator.generate_qr_code(totp_url, filename)
except Exception as e:
print(f"Error: {e}")
graceful_exit()
elif choice == "2": elif choice == "2":
serial = input("Enter Serial: ").strip() show_session_token_instructions()
restore_code = input("Enter Restore Code: ").strip()
if not serial or not restore_code:
print("Serial and Restore Code are required!")
return
print("Retrieving Device Secret...") session_token = input("Enter your Session Token (or type 'exit' to quit): ").strip()
device_info = authenticator.retrieve_device_secret(serial, restore_code) if session_token.lower() == "exit":
device_secret = device_info["deviceSecret"] graceful_exit()
print(f"Device Secret: {device_secret}")
print("Generating TOTP URL...") if not session_token:
base32_secret = authenticator.convert_secret_to_base32(device_secret) print("Session Token is required!")
totp_url = f"otpauth://totp/Battle.net?secret={base32_secret}&digits=8" continue
print(f"TOTP URL: {totp_url}")
print("\nImportant: When importing the key, use these settings:")
print(" - Digits: 8")
print(" - Algorithm: SHA1")
print(" - Timeout: 30 seconds\n")
# Save data and generate QR code try:
filename = f"authenticator_{serial}" print("Fetching Bearer Token...")
authenticator.save_plain_json(f"{filename}.json", {"serial": serial, "restoreCode": restore_code, "deviceSecret": device_secret}) bearer_token = authenticator.get_bearer_token(session_token)
authenticator.generate_qr_code(totp_url, filename) print(f"Bearer Token: {bearer_token}")
serial = input("Enter Serial: ").strip()
restore_code = input("Enter Restore Code: ").strip()
if not serial or not restore_code:
print("Serial and Restore Code are required!")
continue
print("Retrieving Device Secret...")
device_info = authenticator.retrieve_device_secret(serial, restore_code)
device_secret = device_info["deviceSecret"]
print(f"Device Secret: {device_secret}")
print("Generating TOTP URL...")
base32_secret = authenticator.convert_secret_to_base32(device_secret)
totp_url = f"otpauth://totp/Battle.net?secret={base32_secret}&digits=8"
print(f"TOTP URL: {totp_url}")
print("\nImportant: When importing the key, use these settings:")
print(" - Digits: 8")
print(" - Algorithm: SHA1")
print(" - Timeout: 30 seconds")
# Save data and generate QR code
filename = f"authenticator_{serial}"
authenticator.save_plain_json(f"{filename}.json", {"serial": serial, "restoreCode": restore_code, "deviceSecret": device_secret})
authenticator.generate_qr_code(totp_url, filename)
except Exception as e:
print(f"Error: {e}")
graceful_exit()
elif choice == "3": elif choice == "3":
authenticator.reconstruct_totp_from_json()
elif choice == "4":
graceful_exit() graceful_exit()
else: else:
print("Invalid choice!") print("Invalid choice!")
graceful_exit() graceful_exit()
except Exception as e:
print(f"Error: {e}")
graceful_exit()
if __name__ == "__main__": if __name__ == "__main__":
try: try:
+3
View File
@@ -0,0 +1,3 @@
requests
pillow
qrcode