diff --git a/README.md b/README.md index 9c8ce81..82cdd14 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # KeyAuth-Python-Example : Please star 🌟 -KeyAuth Python example for the https://keyauth.cc authentication system. + +KeyAuth Python example SDK for https://keyauth.cc license key API auth. ## **Bugs** -If the default example not added to your software isn't functioning how it should, please report a bug here https://keyauth.cc/app/?page=forms +If you are using our example with no significant changes, and you are having problems, please Report Bug here https://keyauth.cc/app/?page=forms However, we do **NOT** provide support for adding KeyAuth to your project. If you can't figure this out you should use Google or YouTube to learn more about the programming language you want to sell a program in. @@ -27,7 +28,10 @@ Thank you for your compliance, we work hard on the development of KeyAuth and do ## **What is KeyAuth?** -KeyAuth is an Open source authentication system with cloud hosting plans as well. Client SDKs available for [C#](https://github.com/KeyAuth/KeyAuth-CSHARP-Example), [C++](https://github.com/KeyAuth/KeyAuth-CPP-Example), [Python](https://github.com/KeyAuth/KeyAuth-Python-Example), [Java](https://github.com/KeyAuth-Archive/KeyAuth-JAVA-api), [JavaScript](https://github.com/mazkdevf/KeyAuth-JS-Example), [VB.NET](https://github.com/KeyAuth/KeyAuth-VB-Example), [PHP](https://github.com/KeyAuth/KeyAuth-PHP-Example), [Rust](https://github.com/KeyAuth/KeyAuth-Rust-Example), [Go](https://github.com/mazkdevf/KeyAuth-Go-Example), [Lua](https://github.com/mazkdevf/KeyAuth-Lua-Examples), [Ruby](https://github.com/mazkdevf/KeyAuth-Ruby-Example), and [Perl](https://github.com/mazkdevf/KeyAuth-Perl-Example). KeyAuth has several unique features such as memory streaming, webhook function where you can send requests to API without leaking the API, discord webhook notifications, ban the user securely through the application at your discretion. Feel free to join https://t.me/keyauth if you have questions or suggestions. +KeyAuth is a powerful cloud-based authentication system designed to protect your software from piracy and unauthorized access. With KeyAuth, you can implement secure licensing, user management, and subscription systems in minutes. Client SDKs available for [C#](https://github.com/KeyAuth/KeyAuth-CSHARP-Example), [C++](https://github.com/KeyAuth/KeyAuth-CPP-Example), [Python](https://github.com/KeyAuth/KeyAuth-Python-Example), [Java](https://github.com/KeyAuth-Archive/KeyAuth-JAVA-api), [JavaScript](https://github.com/mazkdevf/KeyAuth-JS-Example), [VB.NET](https://github.com/KeyAuth/KeyAuth-VB-Example), [PHP](https://github.com/KeyAuth/KeyAuth-PHP-Example), [Rust](https://github.com/KeyAuth/KeyAuth-Rust-Example), [Go](https://github.com/mazkdevf/KeyAuth-Go-Example), [Lua](https://github.com/mazkdevf/KeyAuth-Lua-Examples), [Ruby](https://github.com/mazkdevf/KeyAuth-Ruby-Example), and [Perl](https://github.com/mazkdevf/KeyAuth-Perl-Example). KeyAuth has several unique features such as memory streaming, webhook function where you can send requests to API without leaking the API, discord webhook notifications, ban the user securely through the application at your discretion. Feel free to join https://t.me/keyauth if you have questions or suggestions. + +> [!TIP] +> https://vaultcord.com FREE Discord bot to Backup server, members, channels, messages & more. Custom verify page, block alt accounts, VPNs & more. ## **Customer connection issues?** @@ -40,7 +44,7 @@ For API, `keyauth.cc` will not work because I purposefully blocked it on there s You can either use Pyinstaller or Nuitka. Links: -- Nutika: https://nuitka.net/ +- Nuitka: https://nuitka.net/ - Pyinstaller: https://pyinstaller.org/ Pyinstaller: @@ -57,10 +61,10 @@ It'll provide you with the code which you should replace with in the `main.py` f ```PY keyauthapp = api( - name = "example", #App name (Manage Applications --> Application name) - ownerid = "JjPMBVlIOd", #Owner ID (Account-Settings --> OwnerID) - secret = "db40d586f4b189e04e5c18c3c94b7e72221be3f6551995adc05236948d1762bc", #App secret(Manage Applications --> App credentials code) - version = "1.0", + name = "", #App name (Manage Applications --> Application name) + ownerid = "", #Owner ID (Account-Settings --> OwnerID) + secret = "", #App secret(Manage Applications --> App credentials code) + version = "", hash_to_check = getchecksum() ) ``` @@ -122,7 +126,7 @@ keyauthapp.register(user, password, license) Used so the user can add extra time to their account by claiming new key. -> **Warning** +> [!Warning] > No password is needed to upgrade account. So, unlike login, register, and license functions - you should **not** log user in after successful upgrade. ```py @@ -226,6 +230,22 @@ Function only works after login. keyauthapp.ban() ``` +## **Enable Two Factor Authentication (2fa)** + +Enable two factor authentication (2fa) on a client account. + +```py +keyauthapp.enable2fa() +``` + +## **Disable Two Factor Authentication (2fa)** + +Disable two factor authentication (2fa) on a client account. + +```py +keyauthapp.disable2fa() +``` + ## **Logout session** Logout the users session and close the application. @@ -239,7 +259,7 @@ keyauthapp.logout() Tutorial video https://www.youtube.com/watch?v=ENRaNPPYJbc -> **Note** +> [!NOTE] > Read documentation for KeyAuth webhooks here https://keyauth.readme.io/reference/webhooks-1 Send HTTP requests to URLs securely without leaking the URL in your application. You should definitely use if you want to send requests to SellerAPI from your application, otherwise if you don't use you'll be leaking your seller key to everyone. And then someone can mess up your application. @@ -263,7 +283,7 @@ data = keyauthapp.webhook("7kR0UedlVI", "", "{\"content\": \"webhook message her ## **Download file** -> **Note** +> [!NOTE] > Read documentation for KeyAuth files here https://docs.keyauth.cc/website/dashboard/files Keep files secure by providing KeyAuth your file download link on the KeyAuth dashboard. Make sure this is a direct download link (as soon as you go to the link, it starts downloading without you clicking anything). The KeyAuth download function provides the bytes, and then you get to decide what to do with those. This example shows how to write it to a file named `text.txt` in the same folder as the program, though you could execute with RunPE or whatever you want. diff --git a/keyauth.py b/keyauth.py index 3987731..4eb9558 100644 --- a/keyauth.py +++ b/keyauth.py @@ -2,11 +2,13 @@ import json as jsond # json import time # sleep before exit import binascii # hex encoding -from uuid import uuid4 # gen random guid import platform # check platform import subprocess # needed for mac device -import hmac # signature checksum -import hashlib # signature checksum +import qrcode +from datetime import datetime, timezone, timedelta +from discord_interactions import verify_key # used for signature verification +from PIL import Image + try: if os.name == 'nt': @@ -28,11 +30,11 @@ class api: - name = ownerid = secret = version = hash_to_check = "" + name = ownerid = version = hash_to_check = "" - def __init__(self, name, ownerid, secret, version, hash_to_check): - if len(ownerid) != 10 and len(secret) != 64: - print("Go to Manage Applications on dashboard, copy python code, and replace code in main.py with that") + def __init__(self, name, ownerid, version, hash_to_check): + if len(ownerid) != 10: + print("Visit https://keyauth.cc/app/, copy Pthon code, and replace code in main.py with that") time.sleep(3) os._exit(1) @@ -40,8 +42,6 @@ def __init__(self, name, ownerid, secret, version, hash_to_check): self.ownerid = ownerid - self.secret = secret - self.version = version self.hash_to_check = hash_to_check self.init() @@ -54,16 +54,11 @@ def init(self): print("You've already initialized!") time.sleep(3) os._exit(1) - - sent_key = str(uuid4())[:16] - - self.enckey = sent_key + "-" + self.secret post_data = { "type": "init", "ver": self.version, "hash": self.hash_to_check, - "enckey": sent_key, "name": self.name, "ownerid": self.ownerid } @@ -96,9 +91,6 @@ def init(self): self.sessionid = json["sessionid"] self.initialized = True - - if json["newSession"]: - time.sleep(0.1) def register(self, user, password, license, hwid=None): self.checkinit() @@ -154,7 +146,7 @@ def upgrade(self, user, license): time.sleep(3) os._exit(1) - def login(self, user, password, hwid=None): + def login(self, user, password, code=None, hwid=None): self.checkinit() if hwid is None: hwid = others.get_hwid() @@ -166,8 +158,11 @@ def login(self, user, password, hwid=None): "hwid": hwid, "sessionid": self.sessionid, "name": self.name, - "ownerid": self.ownerid + "ownerid": self.ownerid, } + + if code is not None: + post_data["code"] = code response = self.__do_request(post_data) @@ -181,7 +176,7 @@ def login(self, user, password, hwid=None): time.sleep(3) os._exit(1) - def license(self, key, hwid=None): + def license(self, key, code=None, hwid=None): self.checkinit() if hwid is None: hwid = others.get_hwid() @@ -194,6 +189,9 @@ def license(self, key, hwid=None): "name": self.name, "ownerid": self.ownerid } + + if code is not None: + post_data["code"] = code response = self.__do_request(post_data) @@ -518,45 +516,124 @@ def logout(self): else: print(json["message"]) time.sleep(3) - os._exit(1) + os._exit(1) + + def enable2fa(self, code=None): + self.checkinit() + + post_data = { + "type": "2faenable", + "sessionid": self.sessionid, + "name": self.name, + "ownerid": self.ownerid, + "code": code + } + + response = self.__do_request(post_data) + + json = jsond.loads(response) + + if json["success"]: + if code is None: + # First request: Display the 2FA secret code + print(f"Your 2FA secret code is: {json['2fa']['secret_code']}") + qr_code = json['2fa']['QRCode'] + self.display_qr_code(qr_code) + code_input = input("Enter the 6 digit 2fa code to enable 2fa: ") + self.enable2fa(code_input); + else: + # Second request: Confirm successful 2FA activation + print("2FA has been successfully enabled!") + time.sleep(3) + else: + print(f"Error: {json['message']}") + time.sleep(3) + os._exit(1) + + def disable2fa(self, code=None): + self.checkinit() + + code = input("Enter the 6 digit 2fa code to disable 2fa: ") + + post_data = { + "type": "2fadisable", + "sessionid": self.sessionid, + "name": self.name, + "ownerid": self.ownerid, + "code": code + } + + response = self.__do_request(post_data) + + json = jsond.loads(response) + + print(json['message']) + time.sleep(3) + + + def display_qr_code(self, qr_code_url): + # Generate QR code image + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + + # Add the QR code URL data + qr.add_data(qr_code_url) + qr.make(fit=True) + + # Create an image from the QR code + img = qr.make_image(fill='black', back_color='white') + + # Display the QR code image + img.show() def __do_request(self, post_data): try: response = requests.post( - "https://keyauth.win/api/1.2/", data=post_data, timeout=10 + "https://keyauth.win/api/1.3/", data=post_data, timeout=10 ) - - key = self.secret if post_data["type"] == "init" else self.enckey - if post_data["type"] == "log": return response.text - - client_computed = hmac.new(key.encode('utf-8'), response.text.encode('utf-8'), hashlib.sha256).hexdigest() - - signature = response.headers["signature"] - if not os.path.exists("C:\\ProgramData\\KeyAuth"): - os.makedirs("C:\\ProgramData\\KeyAuth") - os.makedirs("C:\\ProgramData\\KeyAuth\\Debug") + if post_data["type"] == "log" or post_data["type"] == "file" or post_data["type"] == "2faenable" or post_data["type"] == "2fadisable": + return response.text - exe_name = os.path.basename(__file__) - if not os.path.exists(f"C:\\ProgramData\\KeyAuth\\Debug\\{exe_name}"): - os.makedirs(f"C:\\ProgramData\\KeyAuth\\Debug\\{exe_name}") + # Get the signature and timestamp from the headers + signature = response.headers.get("x-signature-ed25519") + timestamp = response.headers.get("x-signature-timestamp") - with open(f"C:\\ProgramData\\KeyAuth\\Debug\\{exe_name}\\log.txt", "a") as log_file: - if len(response.text) <= 200: - tampered = not hmac.compare_digest(client_computed, signature) - execution_time = time.strftime("%I:%M %p | %m/%d/%Y") - log_file.write(f"\n{execution_time} | {post_data['type']} \nResponse: {response.text}\n Was response tampered with? {tampered}\n") + if not signature or not timestamp: + print("Missing headers for signature verification.") + time.sleep(3) + os._exit(1) + + server_time = datetime.fromtimestamp(int(timestamp), timezone.utc) + current_time = datetime.now(timezone.utc) - if not hmac.compare_digest(client_computed, signature): + #print(f"Server Timestamp (UTC seconds): {timestamp}") + #print(f"Server Time (UTC seconds): {server_time.timestamp()}") + #print(f"Current Time (UTC seconds): {current_time.timestamp()}") + + buffer_seconds = 5 + time_difference = current_time - server_time + + if time_difference > timedelta(seconds=20 + buffer_seconds): + print("Timestamp is too old (exceeded 20 seconds + buffer).") + time.sleep(3) + os._exit(1) + + if not verify_key(response.text.encode('utf-8'), signature, timestamp, '5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b'): print("Signature checksum failed. Request was tampered with or session ended most likely.") - print("Response: " + response.text) time.sleep(3) - os._exit(1) - + os._exit(1) + return response.text - except requests.exceptions.Timeout: - print("Request timed out. Server is probably down/slow at the moment") + except requests.exceptions.Timeout: + print("Request timed out. Server is probably down/slow at the moment") + + class application_data_class: numUsers = numKeys = app_ver = customer_panel = onlineUsers = "" @@ -615,5 +692,4 @@ def get_hwid(): output = subprocess.Popen("ioreg -l | grep IOPlatformSerialNumber", stdout=subprocess.PIPE, shell=True).communicate()[0] serial = output.decode().split('=', 1)[1].replace(' ', '') hwid = serial[1:-2] - return hwid - + return hwid \ No newline at end of file diff --git a/main.py b/main.py index 06e7fac..6fc86d7 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,7 @@ import os import hashlib from time import sleep -from datetime import datetime +from datetime import datetime, UTC # import json as jsond # ^^ only for auto login/json writing/reading @@ -28,11 +28,12 @@ def clear(): if platform.system() == 'Windows': os.system('cls & title Python Example') # clear console, change title elif platform.system() == 'Linux': - os.system('clear') # clear console - sys.stdout.write("\x1b]0;Python Example\x07") # change title + os.system('clear') # Clear the terminal + sys.stdout.write("\033]0;Python Example\007") # Set terminal title + sys.stdout.flush() elif platform.system() == 'Darwin': - os.system("clear && printf '\e[3J'") # clear console - os.system('''echo - n - e "\033]0;Python Example\007"''') # change title + os.system("clear && printf '\033[3J'") # Clear terminal and scrollback + os.system('echo -n -e "\033]0;Python Example\007"') # Set terminal title print("Initializing") @@ -46,10 +47,9 @@ def getchecksum(): keyauthapp = api( - name = "", - ownerid = "", - secret = "", - version = "1.0", + name = "", # App name + ownerid = "", # Account ID + version = "", # Application version. Used for automatic downloads see video here https://www.youtube.com/watch?v=kW195PLCBKs hash_to_check = getchecksum() ) @@ -64,7 +64,8 @@ def answer(): if ans == "1": user = input('Provide username: ') password = input('Provide password: ') - keyauthapp.login(user, password) + code = input('Enter 2fa code: (not using 2fa? Just press enter)') + keyauthapp.login(user, password, code) elif ans == "2": user = input('Provide username: ') password = input('Provide password: ') @@ -76,7 +77,8 @@ def answer(): keyauthapp.upgrade(user, license) elif ans == "4": key = input('Enter your license: ') - keyauthapp.license(key) + code = input('Enter 2fa code: (not using 2fa? Just press enter)') + keyauthapp.license(key, code) else: print("\nInvalid option") sleep(1) @@ -165,7 +167,16 @@ def answer(): print(e) os._exit(1)''' +keyauthapp.fetchStats() +# Display Application Data +print("\nApplication data: ") +print("App Version: " + keyauthapp.app_data.app_ver) +print("Customer Panel Link: " + keyauthapp.app_data.customer_panel) +print("Number of Keys: " + keyauthapp.app_data.numKeys) +print("Number of Users: " + keyauthapp.app_data.numUsers) +print("Online Users: " + keyauthapp.app_data.onlineUsers) +# Display User Data print("\nUser data: ") print("Username: " + keyauthapp.user_data.username) print("IP address: " + keyauthapp.user_data.ip) @@ -174,15 +185,29 @@ def answer(): subs = keyauthapp.user_data.subscriptions # Get all Subscription names, expiry, and timeleft for i in range(len(subs)): sub = subs[i]["subscription"] # Subscription from every Sub - expiry = datetime.utcfromtimestamp(int(subs[i]["expiry"])).strftime( + expiry = datetime.fromtimestamp(int(subs[i]["expiry"]), UTC).strftime( '%Y-%m-%d %H:%M:%S') # Expiry date from every Sub timeleft = subs[i]["timeleft"] # Timeleft from every Sub print(f"[{i + 1} / {len(subs)}] | Subscription: {sub} - Expiry: {expiry} - Timeleft: {timeleft}") -print("Created at: " + datetime.utcfromtimestamp(int(keyauthapp.user_data.createdate)).strftime('%Y-%m-%d %H:%M:%S')) -print("Last login at: " + datetime.utcfromtimestamp(int(keyauthapp.user_data.lastlogin)).strftime('%Y-%m-%d %H:%M:%S')) -print("Expires at: " + datetime.utcfromtimestamp(int(keyauthapp.user_data.expires)).strftime('%Y-%m-%d %H:%M:%S')) +print("Created at: " + datetime.fromtimestamp(int(keyauthapp.user_data.createdate), UTC).strftime('%Y-%m-%d %H:%M:%S')) +print("Last login at: " + datetime.fromtimestamp(int(keyauthapp.user_data.lastlogin), UTC).strftime('%Y-%m-%d %H:%M:%S')) +print("Expires at: " + datetime.fromtimestamp(int(keyauthapp.user_data.expires), UTC).strftime('%Y-%m-%d %H:%M:%S')) + +# Two Factor Authentication +print("\nTwo Factor Authentication:") +print("1. Enable 2FA") +print("2. Disable 2FA") + +tfaans = input("Select Option: ") +if tfaans == "1": + keyauthapp.enable2fa() # You only need to call this once as it's called in the API file. +elif tfaans == "2": + keyauthapp.disable2fa() # You only need to call this once as it's called in the API file, and should ideally only need to be called once anyways. +else: + print("\nInvalid Option") + print("\nExiting in five seconds..") sleep(5) os._exit(1) diff --git a/requirements.txt b/requirements.txt index e2c27ec..b57953d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ requests pywin32 +discord-interactions +qrcode +pillow