轻量级软件授权方案:用Python实现专属激活系统
目录
一 . 简介
二 . 功能步骤
1. 下载python环境二维码依赖包
2. 获取机器唯一uuid代码编写(Windwos,Linux)
3. 生成二维码(利用机器的唯一码)二维码页面展示
4. 激活序列表搭建
5. 生成ak文件(扫描二维码获取机器唯一码)
6. ak文件上传验证(唯一码验证比对)
7. 平台激活序列判断
三 . 总结
一 . 简介
在日常开发中,我们经常会遇到“软件激活”这一场景。无论是商业软件的授权保护,还是内部工具的使用权限控制,激活机制都是确保软件在授权范围内运行的重要手段。通过绑定设备的唯一标识(如 UUID),我们可以实现“一机一码”的激活策略,从而有效防止非法复制与滥用。
本篇博客将以实际项目中的实现为例,介绍如何在 Windows 或 Linux 系统中:
-
获取设备的唯一标识生成二维码;
-
结合客户信息生成专属的激活文件;
-
验证并激活本地软件,并可设置激活有效期。
适合有授权需求的软件开发者参考,本文使用 Python 实现,并配合二维码库生成激活文件,轻量高效。
二 . 功能步骤
1. 下载python环境二维码依赖包
pip install qrcode
2. 获取机器唯一uuid代码编写(Windwos,Linux)
# 获取机器唯一uuid代码编写(Windwos,Linux)
def get_machine_uuid():
system_name = platform.system()
if system_name == "Linux":
try:
return subprocess.check_output("cat /etc/machine-id", shell=True).decode().strip()
except Exception as e:
return f"获取 Linux UUID 出错: {e}"
elif system_name == "Windows":
try:
return subprocess.check_output("wmic csproduct get UUID", shell=True).decode().split("\n")[1].strip()
except Exception as e:
return f"获取 Windows UUID 出错: {e}"
else:
return "不支持的操作系统"
3. 生成二维码(利用机器的唯一码)二维码页面展示
# 激活二维码
@app.route("/uuid_qr")
def generate_qr():
uuid_value = get_machine_uuid()
# 生成二维码
qr = qrcode.QRCode(
version=10, # 控制二维码的尺寸(1~40)
error_correction=qrcode.constants.ERROR_CORRECT_L, # 低级别容错
box_size=10, # 每个格子的像素大小
border=1 # 二维码边框大小
)
qr.add_data(uuid_value)
qr.make(fit=True)
img = qr.make_image(fill="black", back_color="white")
# 将二维码转换为字节流
img_io = io.BytesIO()
img.save(img_io, "PNG")
img_io.seek(0)
return send_file(img_io, mimetype="image/png")
4. 激活序列表搭建
# 序列激活表
class Activate_info(db.Model,TimestampMixin):
"""序列激活表"""
__tablename__ = 't_activate_info'
__table_args__ = {
'mysql_engine': 'InnoDB',
'comment': '序列激活表'
}
# 序列ID
id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='序列ID')
# 用户名称
activate_username = db.Column(db.String(255), comment='用户名称')
# 公司
activate_company = db.Column(db.String(255), comment='公司')
# 国家
activate_nation = db.Column(db.String(255), comment='国家')
# 电话
activate_mobile = db.Column(db.String(255), comment='电话')
# 省
activate_province = db.Column(db.String(255), comment='省')
# 市
activate_city = db.Column(db.String(255), comment='市')
# 地址
activate_address = db.Column(db.String(255), comment='地址')
# 激活时间
activate_time = db.Column(db.String(255), comment='激活时间')
# 激活秘钥
activate_key = db.Column(db.Text, comment='激活秘钥')
# 激活码
activate_encrypted_data = db.Column(db.Text, comment='激活码')
# 激活状态
activate_status = db.Column(db.String(255), comment='激活状态 0未激活 1激活')
# 有效时间
activate_valid_until = db.Column(db.String(255), comment='有效时间')
5. 生成ak文件(扫描二维码获取机器唯一码)
# 产品信息获取生成ak文件
@activate.route('/product_information', methods=['POST'])
def product_information():
# 用户名称
username = strip_whitespace(request.form.get('username', None))
# 公司
company = strip_whitespace(request.form.get('company', None))
# 国家
nation = strip_whitespace(request.form.get('nation', None))
# 电话
mobile = strip_whitespace(request.form.get('mobile', None))
# 省
province = strip_whitespace(request.form.get('province', None))
# 市
city = strip_whitespace(request.form.get('city', None))
# 地址
address = strip_whitespace(request.form.get('address', None))
# 激活时间
time = strip_whitespace(request.form.get('time', None))
# 机器序列code
uuid_code = strip_whitespace(request.form.get('uuid_code', None))
# 添加有效期(例如,有效期30天)
valid_until = strip_whitespace(request.form.get('valid_until', None))
# valid_until = (datetime.now() + timedelta(days=30)).strftime('%Y-%m-%d %H:%M:%S')
# 激活标识key
key = activate_key
# 参数构建判断是否为空
params = [username, company, nation, mobile, province, city, address, time,uuid_code,valid_until]
if not all(params):
return jsonify({'code': 400, 'msg': '设备数据有未填写项'})
# 收集所有信息到一个字典
info = {
'username': username,
'company': company,
'nation': nation,
'mobile': mobile,
'province': province,
'city': city,
'address': address,
'time': time,
'machine_uuid': str(uuid_code),
'key': key,
'date_time': str(datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
'valid_until' : str(valid_until)
}
# 将信息转换为JSON字符串
info_string = json.dumps(info, sort_keys=True)
key = cipher_suite.encrypt(info_string.encode())
info_block = """--------------------------------------------------------- BEGIN Information ---------------------------------------------------------
Software description : 平台激活序列
Extended description : 序列生成
Type : 激活码
--------------------------------------------------------- END Information ---------------------------------------------------------"""
activation_block = f"""--------------------------------------------------------- BEGIN BLOCK ---------------------------------------------------------
{key.decode()}
--------------------------------------------------------- END BLOCK ---------------------------------------------------------"""
activation_block_key = f"""--------------------------------------------------------- BEGIN BLOCK KEY ---------------------------------------------------------
{key_key.decode()}
--------------------------------------------------------- END BLOCK KEY ---------------------------------------------------------"""
# 完整的激活文件内容
ak_file_content = f"""---------------------------------------------------------- Activation File ---------------------------------------------------------
#PLEASE DO NOT MODIFY THIS FILE. MAKING ANY CHANGE TO THIS FILE WILL INVALIDATE YOUR LICENSE.
Copyright (C) 2024 Your Company Name. All rights reserved.
{info_block}
{activation_block}
{activation_block_key}
"""
# 在 static 目录下新建 ak_create 目录
ak_create_path = os.path.join(FILE_SAVE_PATH, 'ak_create')
os.makedirs(ak_create_path, exist_ok=True,) # 如果目录不存在则创建
# ak文件
ak_name = 'activation_file_{}.ak'.format(datetime.now().strftime('%Y%m%d%H%M%S'))
# 拼接完整的文件保存路径
file_path = os.path.join(ak_create_path, ak_name)
# 确保目录存在
os.makedirs(ak_create_path, exist_ok=True)
# 写入文件
with open(file_path, "w",encoding='utf-8') as file:
file.write(ak_file_content)
return jsonify(
{'code': 200, 'msg': 'ak激活码文件生成', 'file': 'http://{}:5000/static/ak_create/{}'.format(DB_HOST, ak_name)})
6. ak文件上传验证(唯一码验证比对)
# ak激活文件上传及验证
@activate.route('/product_upload', methods=['POST'])
def product_upload():
def extract_activation_key(file_path):
def clean_text(text):
# 使用正则表达式去除上下的两条线和换行符
cleaned_text = re.sub(r'^\s*-+\s*|\s*-+\s*$', '', text, flags=re.MULTILINE)
return cleaned_text.strip()
with open(file_path, 'r',encoding='utf-8') as file:
content = file.read()
# 使用正则表达式提取 BEGIN BLOCK 和 END BLOCK 之间的内容
match = re.search(
r'BEGIN BLOCK\s*(.*?)\s* END BLOCK',
content, re.DOTALL)
match_key = re.search(
r'BEGIN BLOCK KEY\s*(.*?)\s* END BLOCK KEY',
content, re.DOTALL)
if match:
activation_key = match.group(1).strip()
activation_key_key = match_key.group(1).strip()
return clean_text(activation_key), clean_text(activation_key_key)
else:
return "文件中没有找到激活码!"
# 解析ak文件内容
files = request.files.get('files', None)
# 判断当文件存在时
if files:
# 在 static 目录下新建 ak_activate 目录
ak_activate_path = os.path.join(FILE_SAVE_PATH, 'ak_activate')
os.makedirs(ak_activate_path, exist_ok=True) # 如果目录不存在则创建
# 文件名
filename = files.filename
# 拼接文件保存路径
file_path = os.path.join(ak_activate_path, filename)
# 将文件保存到 ak_activate 目录下
files.save(file_path)
try:
# 提取密钥和加密数据
res = extract_activation_key(file_path)
# 进行编码处理
key = res[1].encode() # 从请求中获取密钥
encrypted_data = res[0].encode() # 从请求中获取加密数据
print(encrypted_data)
# 解密
cipher_suite = Fernet(key)
decrypted_info = cipher_suite.decrypt(encrypted_data).decode()
# 将解密后的 JSON 字符串转换为字典
info_dict = json.loads(decrypted_info)
print(info_dict)
if not info_dict.get('valid_until'):
return jsonify({'code': 400, 'msg': 'ak导入校验错误,请导入正确ak激活序列文件!'})
# 对秘钥进行比对,如果比对成功,数据写入,并且系统数据状态发生改变
if info_dict.get('key') != activate_key:
return jsonify({'code': 400, 'msg': 'ak导入校验错误,请导入正确ak激活序列文件!'})
if info_dict.get('machine_uuid') != get_machine_uuid():
return jsonify({'code':400,'msg':'ak导入校验错误,机器编码对比错误!'})
# 判断key跟激活码是否存在,如果存在代表该激活码已经实用,请重新获取激活码
res = db.session.query(Activate_info).filter(Activate_info.activate_key == key,
Activate_info.activate_encrypted_data == encrypted_data,
Activate_info.activate_status == 0,
).first()
if res:
return jsonify({'code': 400, 'msg': '该激活码已经使用,请重新获取激活码!'})
activate_data = Activate_info(
activate_username=info_dict.get('username'),
activate_company=info_dict.get('company'),
activate_nation=info_dict.get('nation'),
activate_mobile=info_dict.get('mobile'),
activate_province=info_dict.get('province'),
activate_city=info_dict.get('city'),
activate_address=info_dict.get('address'),
activate_time=info_dict.get('time'),
activate_key=key,
activate_encrypted_data=encrypted_data,
activate_status='1',
activate_valid_until=info_dict.get('valid_until'),
)
db.session.add(activate_data)
db.sessionmit()
# 装饰器,监听激活码数据时间,如果该时间到期,状态更改,如果为永久则不昨改动
except FileNotFoundError:
return jsonify({'code': 400, 'msg': 'ak导入校验错误,请导入正确ak激活序列文件!'})
except InvalidToken:
return jsonify({'code': 400, 'msg': 'ak导入校验错误,请导入正确ak激活序列文件!'})
except json.JSONDecodeError:
return jsonify({'code': 400, 'msg': 'ak导入校验错误,请导入正确ak激活序列文件!'})
except Exception as e:
print(e,flush=True)
return jsonify({'code': 400, 'msg': 'ak导入校验错误,请导入正确ak激活序列文件!'})
return jsonify({'code': 200, 'msg': '系统激活成功!', })
else:
return jsonify({'code': 400, 'msg': 'ak文件未上传!', })
7. 平台激活序列判断
# 平台激活序列判断
@activate.route('/activate_verify', methods=['GET'])
def activate_verify():
# 获取激活码序列数据及对应状态确认系统
res = db.session.query(Activate_info).filter(Activate_info.activate_status==1).all()
# 标记是否有有效的激活记录
valid_activation_found = False
if not res:
return jsonify({"msg": "该系统尚未激活!请登录运管中心授权!", 'code': 400})
else:
for i in res:
current_time = datetime.now()
valid_until = datetime.strptime(i.activate_valid_until, '%Y-%m-%d')
if valid_until:
if current_time > valid_until:
i.activate_status = 0
else:
valid_activation_found = True
db.sessionmit()
if valid_activation_found:
return jsonify({"msg": "系统已激活!", 'code': 200})
else:
return jsonify({"msg": "系统激活序列已过期!!", 'code': 400})
8. 接口装饰器判断(对系统接口进行请求需验证token及系统是否激活)
# --------装饰器--------
def jwt_required_with_refresh(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
current_user = get_jwt_identity()
#获取用户信息(数据库查询)
user_info = db.session.query(User).filter(User.username == current_user).first()
if not user_info:
return jsonify({'msg': '用户不存在!'}), 401 # 用户不存在,返回 401 未授权
activations = db.session.query(Activate_info).filter(Activate_info.activate_status==1).first()
if not activations:
return jsonify({"msg": "该系统尚未激活!请登录运管中心授权!", 'code': 400})
# 继续执行原函数
return fn(*args, **kwargs)
return wrapper
三 . 总结
在本文中,我们介绍了如何为软件开发实现设备绑定激活机制。通过获取设备的唯一标识(如UUID)并结合客户信息生成专属激活文件,确保软件只能在授权设备上运行,从而有效防止非法复制和滥用。我们还探讨了如何生成二维码来便捷地展示激活信息,并通过设置激活有效期来管理软件的使用期限。整个实现过程轻量高效,适用于需要授权保护的软件开发者,尤其是利用Python语言和二维码库来生成激活文件。