优化激活码逻辑
This commit is contained in:
@@ -1,23 +1,65 @@
|
||||
# generate_activation_code.py
|
||||
import hashlib
|
||||
import uuid
|
||||
import random # For date-based index generation
|
||||
|
||||
# --- Configuration (MUST MATCH THE ONE IN THE MAIN APP) ---
|
||||
SECRET_KEY = "UgG2jVcqmYed%i4ZVRC%JN*&%6^LR!%y%dy4sE4c9zZ" # Keep this very secret
|
||||
ACTIVATION_BASE_STRING = "yBSksSte52uorfNSSmxLSpLxoteRVd2i8enB73mFpXj"
|
||||
CODE_LENGTH = 16 # How many characters of the hash to use
|
||||
# --- Seed Date Configuration (MUST MATCH THE ONE IN THE MAIN APP) ---
|
||||
SEED_YEAR = 2025
|
||||
SEED_MONTH = 5
|
||||
SEED_DAY = 18
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
# --- New Configuration ---
|
||||
# No other specific configuration needed for UUID-based generation itself.
|
||||
# Key derivation logic is embedded in the function.
|
||||
# ---------------------------------------------------------
|
||||
|
||||
def generate_code():
|
||||
"""Generates the activation code."""
|
||||
data_to_hash = ACTIVATION_BASE_STRING + SECRET_KEY
|
||||
hashed_data = hashlib.sha256(data_to_hash.encode('utf-8')).hexdigest()
|
||||
raw_code = hashed_data[:CODE_LENGTH].upper()
|
||||
"""Generates a UUID and a corresponding activation key."""
|
||||
generated_uuid = uuid.uuid4()
|
||||
uuid_str = str(generated_uuid)
|
||||
|
||||
# New, more complex key derivation logic:
|
||||
# 1. Remove hyphens from the UUID string.
|
||||
# 2. Define a fixed set of indices.
|
||||
# 3. Extract characters at these indices.
|
||||
# 4. Concatenate and uppercase them to form the 8-character key.
|
||||
# This logic MUST match the validation logic in the main application.
|
||||
|
||||
s = uuid_str.replace('-', '') # Should be 32 characters long
|
||||
|
||||
# Generate dynamic indices based on the fixed SEED_DATE
|
||||
date_seed_value = SEED_YEAR * 10000 + SEED_MONTH * 100 + SEED_DAY
|
||||
rng = random.Random(date_seed_value)
|
||||
all_possible_indices = list(range(32))
|
||||
rng.shuffle(all_possible_indices)
|
||||
dynamic_indices = sorted(all_possible_indices[:8]) # Take first 8 and sort for consistency
|
||||
|
||||
if len(s) == 32: # Standard UUID without hyphens
|
||||
try:
|
||||
key_chars = [s[i] for i in dynamic_indices]
|
||||
activation_key_raw = "".join(key_chars).upper()
|
||||
except IndexError:
|
||||
# This should ideally not happen if s is 32 chars and indices are from range(32)
|
||||
activation_key_raw = "IDXERROR"
|
||||
else:
|
||||
# Fallback or error handling if s is not 32 chars
|
||||
activation_key_raw = "LENERROR"
|
||||
|
||||
# Format the code e.g., XXXX-XXXX-XXXX-XXXX for 16 chars
|
||||
formatted_code = "-".join(raw_code[i:i+4] for i in range(0, len(raw_code), 4))
|
||||
return formatted_code
|
||||
# Optionally format the key for display (e.g., XXXX-XXXX)
|
||||
# formatted_key = "-".join(activation_key_raw[i:i+4] for i in range(0, len(activation_key_raw), 4))
|
||||
# For simplicity, we'll return the raw 8-char key and let the main app decide on display formatting if needed.
|
||||
|
||||
return uuid_str, activation_key_raw
|
||||
|
||||
if __name__ == "__main__":
|
||||
activation_code = generate_code()
|
||||
print(f"Generated Activation Code: {activation_code}")
|
||||
print("Keep this code safe and provide it to your users.")
|
||||
generated_uuid, activation_key = generate_code()
|
||||
|
||||
print(f"Generated UUID: {generated_uuid}")
|
||||
print(f"Generated Activation Key: {activation_key}")
|
||||
# Example of how it might be formatted for easier user input:
|
||||
if len(activation_key) == 8:
|
||||
formatted_display_key = f"{activation_key[:4]}-{activation_key[4:]}"
|
||||
print(f"Suggested Key Format for User: {formatted_display_key}")
|
||||
|
||||
print("\nProvide both the UUID and the Activation Key to the user.")
|
||||
print("The user will need to enter both into the application to activate.")
|
||||
|
||||
@@ -9,12 +9,11 @@ from datetime import date, timedelta # Added timedelta
|
||||
from pypinyin import pinyin, Style
|
||||
import json # Added for trial status
|
||||
import sys # Import sys for path joining
|
||||
import hashlib # For activation
|
||||
import random # For date-based index generation in ActivationManager
|
||||
|
||||
# --- Activation Configuration (MUST MATCH generate_activation_code.py) ---
|
||||
SECRET_KEY = "UgG2jVcqmYed%i4ZVRC%JN*&%6^LR!%y%dy4sE4c9zZ"
|
||||
ACTIVATION_BASE_STRING = "yBSksSte52uorfNSSmxLSpLxoteRVd2i8enB73mFpXj"
|
||||
CODE_LENGTH = 16
|
||||
# --- Activation Configuration (UUID Based) ---
|
||||
# Old SECRET_KEY, ACTIVATION_BASE_STRING, CODE_LENGTH are no longer used for UUID activation.
|
||||
# New key length is effectively 8 characters, derived from UUID.
|
||||
APP_NAME_FOR_CONFIG = "RollCallApp" # Used for storing activation status
|
||||
# ---------------------------------------------------------
|
||||
|
||||
@@ -22,9 +21,15 @@ APP_NAME_FOR_CONFIG = "RollCallApp" # Used for storing activation status
|
||||
TRIAL_DURATION_DAYS = 1
|
||||
MAX_TRIAL_USES = 3
|
||||
# Version for activation and trial files, ensures a fresh trial if activation logic changes
|
||||
APP_CONFIG_VERSION_SUFFIX = "_v28"
|
||||
APP_CONFIG_VERSION_SUFFIX = "_v30_uuid_date_idx" # << UPDATED FOR NEW ACTIVATION
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# --- Seed Date Configuration (MUST MATCH THE ONE IN acitvation_code.py) ---
|
||||
SEED_YEAR = 2025
|
||||
SEED_MONTH = 5
|
||||
SEED_DAY = 18
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
class ActivationManager:
|
||||
def __init__(self):
|
||||
self.config_dir = self._get_config_dir()
|
||||
@@ -51,48 +56,67 @@ class ActivationManager:
|
||||
path = os.path.join(os.path.expanduser('~/.config'), APP_NAME_FOR_CONFIG)
|
||||
return path
|
||||
|
||||
def _generate_expected_code(self):
|
||||
"""Generates the expected activation code based on embedded secrets."""
|
||||
data_to_hash = ACTIVATION_BASE_STRING + SECRET_KEY
|
||||
hashed_data = hashlib.sha256(data_to_hash.encode('utf-8')).hexdigest()
|
||||
raw_code = hashed_data[:CODE_LENGTH].upper()
|
||||
formatted_code = "-".join(raw_code[i:i+4] for i in range(0, len(raw_code), 4))
|
||||
return formatted_code
|
||||
def _generate_expected_key_from_uuid(self, uuid_str):
|
||||
"""
|
||||
Generates the expected activation key from a given UUID string.
|
||||
This logic MUST match generate_activation_code.py.
|
||||
"""
|
||||
if not uuid_str:
|
||||
return ""
|
||||
|
||||
s = uuid_str.replace('-', '').lower() # Normalize to lowercase and remove hyphens
|
||||
|
||||
if len(s) != 32: # Standard UUID without hyphens should be 32 chars
|
||||
return "" # Invalid UUID format for key derivation
|
||||
|
||||
# Generate dynamic indices based on the fixed SEED_DATE (must match acitvation_code.py)
|
||||
date_seed_value = SEED_YEAR * 10000 + SEED_MONTH * 100 + SEED_DAY
|
||||
rng = random.Random(date_seed_value) # random was imported at the top
|
||||
all_possible_indices = list(range(32))
|
||||
rng.shuffle(all_possible_indices)
|
||||
dynamic_indices = sorted(all_possible_indices[:8]) # Take first 8 and sort
|
||||
|
||||
try:
|
||||
key_chars = [s[i] for i in dynamic_indices]
|
||||
expected_key = "".join(key_chars).upper()
|
||||
return expected_key
|
||||
except IndexError:
|
||||
# This should not happen if len(s) == 32 and indices are correct
|
||||
return ""
|
||||
|
||||
def normalize_code(self, code_str):
|
||||
"""Normalizes user-entered code: uppercase, remove hyphens and spaces."""
|
||||
"""Normalizes user-entered activation KEY: uppercase, remove hyphens and spaces."""
|
||||
if not code_str: return ""
|
||||
return code_str.upper().replace("-", "").replace(" ", "")
|
||||
|
||||
def format_code_for_display(self, raw_code_str):
|
||||
"""Formats a raw code string (e.g., ABCDEFGH...) into XXXX-XXXX..."""
|
||||
if not raw_code_str or len(raw_code_str) != CODE_LENGTH:
|
||||
return raw_code_str # Or handle error
|
||||
return "-".join(raw_code_str[i:i+4] for i in range(0, len(raw_code_str), 4))
|
||||
def format_code_for_display(self, raw_key_str):
|
||||
"""Formats an 8-character raw key string into XXXX-XXXX."""
|
||||
if not raw_key_str or len(raw_key_str) != 8:
|
||||
return raw_key_str # Return as is if not 8 chars
|
||||
return f"{raw_key_str[:4]}-{raw_key_str[4:]}"
|
||||
|
||||
|
||||
def is_activated(self):
|
||||
"""Checks if the application is already activated."""
|
||||
if os.path.exists(self.activation_file):
|
||||
try:
|
||||
with open(self.activation_file, 'r') as f:
|
||||
stored_code_normalized = self.normalize_code(f.read().strip())
|
||||
expected_code_normalized = self.normalize_code(self._generate_expected_code())
|
||||
return stored_code_normalized == expected_code_normalized
|
||||
except Exception:
|
||||
return False # Problem reading file, assume not activated
|
||||
return False
|
||||
"""
|
||||
Checks if the application is already activated by checking for the activation file.
|
||||
The activation file, if present, contains the UUID used for activation.
|
||||
"""
|
||||
return os.path.exists(self.activation_file)
|
||||
|
||||
def activate(self, entered_code):
|
||||
"""Attempts to activate the application with the given code."""
|
||||
normalized_entered_code = self.normalize_code(entered_code)
|
||||
expected_code = self._generate_expected_code()
|
||||
normalized_expected_code = self.normalize_code(expected_code)
|
||||
def activate(self, entered_uuid, entered_key):
|
||||
"""Attempts to activate the application with the given UUID and key."""
|
||||
if not entered_uuid or not entered_key:
|
||||
return False
|
||||
|
||||
if normalized_entered_code == normalized_expected_code:
|
||||
normalized_entered_uuid = entered_uuid.strip().lower() # UUIDs are case-insensitive in hex but usually lowercase
|
||||
normalized_entered_key = self.normalize_code(entered_key) # Uppercase, no hyphens/spaces
|
||||
|
||||
expected_key = self._generate_expected_key_from_uuid(normalized_entered_uuid)
|
||||
|
||||
if normalized_entered_key == expected_key and expected_key != "": # Ensure expected_key is not empty
|
||||
try:
|
||||
with open(self.activation_file, 'w') as f:
|
||||
f.write(expected_code) # Store the formatted expected code
|
||||
f.write(normalized_entered_uuid) # Store the UUID
|
||||
# If activated, trial is no longer relevant, remove trial file
|
||||
if os.path.exists(self.trial_status_file):
|
||||
try:
|
||||
@@ -106,17 +130,25 @@ class ActivationManager:
|
||||
return False
|
||||
|
||||
def prompt_for_activation(self, master):
|
||||
"""Shows a dialog to prompt for activation code."""
|
||||
"""Shows dialogs to prompt for UUID and activation key."""
|
||||
while True:
|
||||
code = simpledialog.askstring("请激活", "------>请输入激活码以使用小程序<------", parent=master)
|
||||
if code is None: # User cancelled
|
||||
return False # Activation failed or cancelled
|
||||
|
||||
if self.activate(code):
|
||||
uuid_str = simpledialog.askstring("激活 - 步骤 1/2",
|
||||
"请输入您的 UUID:",
|
||||
parent=master)
|
||||
if uuid_str is None: # User cancelled
|
||||
return False
|
||||
|
||||
key_str = simpledialog.askstring("激活 - 步骤 2/2",
|
||||
"请输入您的激活密钥 (例如: ABCD-EFGH):",
|
||||
parent=master)
|
||||
if key_str is None: # User cancelled
|
||||
return False
|
||||
|
||||
if self.activate(uuid_str, key_str):
|
||||
messagebox.showinfo("激活成功", "软件已成功激活!感谢使用。", parent=master)
|
||||
return True
|
||||
else:
|
||||
retry = messagebox.askretrycancel("激活失败", "激活码无效或输入错误,请重试。", parent=master)
|
||||
retry = messagebox.askretrycancel("激活失败", "UUID 或激活密钥无效,请重试。", parent=master)
|
||||
if not retry:
|
||||
return False # User chose not to retry
|
||||
|
||||
|
||||
Reference in New Issue
Block a user