优化激活码逻辑

This commit is contained in:
2025-05-18 22:37:54 +08:00
parent 1edb2dd157
commit fbac9d8019
2 changed files with 132 additions and 58 deletions

View File

@@ -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.")

View File

@@ -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