├── README.md ├── build └── main.py ├── client ├── __pycache__ │ ├── license_getter.cpython-37.pyc │ └── license_verifier.cpython-37.pyc ├── license_getter.py ├── license_verifier.py ├── main.py └── tools │ ├── gen_license_linux.py │ ├── gen_license_mac.py │ └── gen_license_win.py ├── images └── frame.png └── server ├── assets └── logo.png ├── data └── 20211112 │ └── 7ac36daa-6000-441c-a23a-098ba0d2df34 │ ├── 20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.aes │ ├── 20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.ori │ ├── 20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_private.pem │ ├── 20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_public.pem │ └── License.lic ├── func.py ├── gui.py └── main.py /README.md: -------------------------------------------------------------------------------- 1 | # Python 项目加密部署及License生成和验证 2 | 3 | --- 4 | #### 概要 5 | 6 | 旨在为基于python开发的各类工具、服务端的简单部署提供解决方案 7 | 8 | **注:** 不适用于大型项目、商用部署 9 | 10 | 具有保护源码、防止复制、时间控制等功能,可以用于各种操作系统及基于docker的部署环境 11 | 12 | - **保护代码:** 通过Cython编译为pyd或so文件 13 | - **防止复制:** 获取和验证机器的唯一标识 14 | - **时间控制:** 在License中加入失效时间 15 | 16 | --- 17 | #### 使用 18 | 19 | #### 环境 20 | 21 | ```shell 22 | pip install pycryptodome Cython 23 | ``` 24 | **注:** Cython仅用于编译 25 | 26 | #### 结构和功能 27 | 28 | **./server** 秘钥管理 29 | - assets : ui界面资源 30 | - data : 储存生成的秘钥文件 31 | - func.py : 生成秘钥和加密License 32 | - gui.py : ui界面 33 | - main.py : ui界面入口 34 | 35 | 使用时直接运行: 36 | ```shell 37 | python main.py 38 | ``` 39 | 40 | **./client** 集成部署在客户端 41 | - license_getter.py : 获取机器标识,生成License 42 | - license_verifier.py : 验证License 43 | - main.py : 调用示例 44 | 45 | 集成时,main.py中的调用过程必须编译,且对关键函数重命名或重写 46 | 47 | **./build** 用于编译python源码,使用时先修改main.py中的待编译文件列表 48 | ```shell 49 | python main.py build_ext --inplace 50 | ``` 51 | 52 | #### 集成示例 53 | 54 | 以一个简单的flask项目为例 55 | 56 | 在app.py中,定义app启动函数,在未检测到License文件时,调用get_license获取机器标识,在检测到有License时,调用is_license_valid判断License是否有效: 57 | 58 | ```python 59 | from license_verifier import is_license_valid 60 | from license_getter import get_license 61 | 62 | def app_run(): 63 | if not os.path.exists(lic_file): 64 | get_license(lic_file) 65 | print('License is generated') 66 | return 67 | if is_license_valid(lic_file=lic_file, aes_key_file=aes_key, rsa_key_file=rsa_key): 68 | app.run(host=host, port=port, threaded=True) 69 | else: 70 | print('License is invalid') 71 | return 72 | ``` 73 | **注:** 如前文,实际情况下先将关键函数重命名或重写,提高安全性 74 | 75 | 部署时,编译app.py, license_getter.py, license_verifier.py,设置manage.py作为启动入口 76 | 77 | ```python 78 | from app import app_run 79 | 80 | if __name__ == '__main__': 81 | app_run() 82 | ``` 83 | 84 | --- 85 | #### 具体实现 86 | ![Alt](https://github.com/sunchang272/python-license/blob/main/images/frame.png) 87 | 88 | 原理非常简单,如上图所示 89 | 90 | 91 | -------------------------------------------------------------------------------- /build/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: main.py 6 | Author: sunch 7 | Create Date: 2021/11/12 13:48 8 | ------------------------------------------------- 9 | """ 10 | 11 | # python main.py build_ext --inplace 12 | from Cython.Build import cythonize 13 | from distutils.core import setup 14 | 15 | setup( 16 | name='python-license', 17 | ext_modules=cythonize([ 18 | '../server/gui.py', 19 | '../server/func.py', 20 | '../client/license_getter.py', 21 | '../client/license_verifier.py' 22 | ]), 23 | ) 24 | -------------------------------------------------------------------------------- /client/__pycache__/license_getter.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunchang272/python-license/fcdddcb7714b3ee514d36a6afbdab92c31779c50/client/__pycache__/license_getter.cpython-37.pyc -------------------------------------------------------------------------------- /client/__pycache__/license_verifier.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunchang272/python-license/fcdddcb7714b3ee514d36a6afbdab92c31779c50/client/__pycache__/license_verifier.cpython-37.pyc -------------------------------------------------------------------------------- /client/license_getter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: license_getter.py 6 | Author: sunch 7 | Create Date: 2021/11/12 9:36 8 | ------------------------------------------------- 9 | """ 10 | 11 | import hashlib 12 | import socket 13 | import base64 14 | import uuid 15 | import os 16 | 17 | 18 | class Utils: 19 | # select any to combine license 20 | # below functions can work in most dockers, but not absolutely safe 21 | # without docker, refer the methods in ./client/tools 22 | @staticmethod 23 | def format_lic(chars): 24 | # defined your format and head 25 | return '{0}_{1}'.format('PyAuth', chars.decode()).encode() 26 | 27 | @staticmethod 28 | def get_mac_address(): 29 | # ref: https://zhuanlan.zhihu.com/p/155951909 30 | mac = uuid.uuid1().hex[-12:] 31 | return ':'.join([mac[e:e + 2] for e in range(0, 11, 2)]) 32 | 33 | @staticmethod 34 | def get_cpu_info(): 35 | return os.popen('cat /proc/cpuinfo').read() 36 | 37 | @staticmethod 38 | def get_hostname(): 39 | return socket.gethostname() 40 | 41 | @staticmethod 42 | def get_os_info(): 43 | # For docker, the second position means the container id 44 | # The last position may be different either in same device 45 | return ' '.join(os.popen('uname -a').read().split(' ')[2:-1]) 46 | 47 | @staticmethod 48 | def sha256_enc(chars): 49 | sha256 = hashlib.sha256() 50 | sha256.update(chars) 51 | return sha256.digest() 52 | 53 | @staticmethod 54 | def md5_enc(chars): 55 | md5 = hashlib.md5() 56 | md5.update(chars) 57 | return md5.digest() 58 | 59 | @staticmethod 60 | def base64_enc(chars): 61 | return base64.encodebytes(chars).strip(b'\n') 62 | 63 | 64 | class GenLic: 65 | def __init__(self, out_path): 66 | self.out_path = out_path 67 | self.license = self.gen_license() 68 | 69 | @staticmethod 70 | def gen_license(): 71 | try: 72 | ori_lic = Utils.get_mac_address().encode() 73 | return Utils.base64_enc(ori_lic) 74 | except Exception as e: 75 | print(e) 76 | return None 77 | 78 | def save_license(self): 79 | lic_path = os.path.join(self.out_path).replace('\\', '/') 80 | with open(lic_path, 'wb') as lic_writer: 81 | lic_writer.write(self.license) 82 | 83 | 84 | def get_license(out_path): 85 | lic_gen = GenLic(out_path) 86 | lic_gen.save_license() 87 | 88 | 89 | if __name__ == '__main__': 90 | get_license('./License.lic') 91 | -------------------------------------------------------------------------------- /client/license_verifier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: license_verifier.py 6 | Author: sunch 7 | Create Date: 2021/11/12 9:36 8 | ------------------------------------------------- 9 | """ 10 | 11 | from Crypto.Cipher import PKCS1_OAEP, AES 12 | from Crypto.PublicKey import RSA 13 | from license_getter import GenLic 14 | import datetime 15 | import base64 16 | import os 17 | 18 | 19 | class Utils: 20 | @staticmethod 21 | def base64_decode(chars): 22 | return base64.decodebytes(chars) 23 | 24 | @staticmethod 25 | def rsa_decrypt(chars, pri_key): 26 | rsa_decrypt = PKCS1_OAEP.new(key=pri_key) 27 | return rsa_decrypt.decrypt(chars) 28 | 29 | @staticmethod 30 | def aes_decrypt(chars, aes_key, mode=AES.MODE_ECB): 31 | aes = AES.new(aes_key, mode) 32 | return aes.decrypt(chars) 33 | 34 | @staticmethod 35 | def format_license(chars): 36 | hw_info, due_time, remark = chars.decode().split('\n') 37 | return hw_info.encode(), due_time[:19], remark 38 | 39 | 40 | class VerifyLic: 41 | def __init__(self, lic_file, aes_key_file, rsa_key_file): 42 | self.lic_file = lic_file 43 | self.aes_key_file = aes_key_file 44 | self.rsa_key_file = rsa_key_file 45 | self.license = self.load_lic() 46 | self.rsa_pri_key = self.load_rsa_key() 47 | self.aes_key = self.load_aes_key() 48 | self.decrypted_aes_key = self.decrypt_aes_key() 49 | self.hw_info, self.due_time, self.remark = self.decrypt_lic() 50 | 51 | def load_lic(self): 52 | if not os.path.exists(self.lic_file): 53 | return None 54 | return open(self.lic_file, 'rb').read() 55 | 56 | def load_rsa_key(self): 57 | if not os.path.exists(self.rsa_key_file): 58 | return None 59 | try: 60 | primary_key = open(self.rsa_key_file, 'rb').read() 61 | return RSA.import_key(primary_key) 62 | except Exception as e: 63 | print(e) 64 | return None 65 | 66 | def load_aes_key(self): 67 | if not os.path.exists(self.aes_key_file): 68 | return None 69 | return open(self.aes_key_file, 'rb').read() 70 | 71 | def decrypt_aes_key(self): 72 | if self.aes_key is None or self.rsa_pri_key is None: 73 | return None 74 | try: 75 | rsa_decrypted_key = Utils.rsa_decrypt(self.aes_key, self.rsa_pri_key) 76 | return Utils.base64_decode(rsa_decrypted_key) 77 | except Exception as e: 78 | print(e) 79 | return None 80 | 81 | def decrypt_lic(self): 82 | if self.decrypted_aes_key is None or self.license is None: 83 | return None 84 | try: 85 | base64_decoded_license = Utils.base64_decode(self.license) 86 | aes_decrypted_license = Utils.aes_decrypt(base64_decoded_license, self.decrypted_aes_key) 87 | return Utils.format_license(aes_decrypted_license) 88 | except Exception as e: 89 | print(e) 90 | return None 91 | 92 | def verify_hw_info(self): 93 | hw_info = GenLic.gen_license() 94 | return hw_info == self.hw_info 95 | 96 | def verify_due_time(self): 97 | try: 98 | now_time = datetime.datetime.now() 99 | due_time = datetime.datetime.strptime(self.due_time, '%Y-%m-%d %H:%M:%S') 100 | return now_time < due_time 101 | except Exception as e: 102 | print(e) 103 | return False 104 | 105 | def verify_license(self): 106 | return self.verify_hw_info() and self.verify_due_time() 107 | 108 | 109 | def is_license_valid(lic_file, aes_key_file, rsa_key_file): 110 | # Files call this function must be compiled 111 | if not os.path.exists(lic_file) or not os.path.exists(aes_key_file) or not os.path.exists(rsa_key_file): 112 | return False 113 | verifier = VerifyLic(lic_file, aes_key_file, rsa_key_file) 114 | return verifier.verify_license() 115 | 116 | 117 | if __name__ == '__main__': 118 | is_invalid = is_license_valid('./License.lic', './20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.aes', 119 | './20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_private.pem') 120 | print(is_invalid) 121 | -------------------------------------------------------------------------------- /client/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: main.py.py 6 | Author: sunch 7 | Create Date: 2021/11/12 13:57 8 | ------------------------------------------------- 9 | """ 10 | 11 | """ 12 | This file is just for tests 13 | In real environments, files call these functions must be compiled 14 | """ 15 | from license_verifier import is_license_valid 16 | from license_getter import get_license 17 | 18 | if __name__ == '__main__': 19 | """ 20 | 1. Get private key and aes key from ../server 21 | 2. Get License by get_license 22 | 3. Encrypt License in ../server 23 | 4. Call is_license_valid to verify License 24 | """ 25 | get_license(out_path='./') 26 | is_license_valid(lic_file='./License.lic', 27 | aes_key_file='./20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.aes', 28 | rsa_key_file='./20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_private.pem') 29 | -------------------------------------------------------------------------------- /client/tools/gen_license_linux.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: gen_license_linux.py.py 6 | Author: sunch 7 | Create Date: 2021/11/11 14:47 8 | ------------------------------------------------- 9 | """ 10 | 11 | 12 | class GenLicLinux: 13 | """ 14 | ------------------------------------------------- 15 | 1. 直接读取 /proc/cpuinfo 文件信息 16 | 2. lscpu 或 lspci 读硬件信息 17 | 3. ifconfig / ip link show 读网卡信息 18 | 4. dmidecode,参考:https://www.cnblogs.com/gmhappy/p/11863968.html 19 | 5. uuid1读最后的Mac地址 20 | ------------------------------------------------- 21 | """ 22 | def __init__(self): 23 | pass 24 | 25 | def get_cpu_id(self): 26 | pass 27 | -------------------------------------------------------------------------------- /client/tools/gen_license_mac.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: gen_license_mac.py 6 | Author: sunch 7 | Create Date: 2021/11/11 15:02 8 | ------------------------------------------------- 9 | """ 10 | 11 | 12 | # 没有用过,男儿当自强 13 | 14 | -------------------------------------------------------------------------------- /client/tools/gen_license_win.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: gen_license_win.py 6 | Author: sunch 7 | Create Date: 2021/11/11 14:42 8 | ------------------------------------------------- 9 | """ 10 | 11 | 12 | class GenLicWin: 13 | """ 14 | ------------------------------------------------- 15 | 1. 参考https://www.cnblogs.com/one-tom/p/13403141.html 16 | 2. 通过调用Powershell执行Get-WmiObject命令查询 17 | 3. uuid1 18 | ------------------------------------------------- 19 | """ 20 | def __init__(self): 21 | pass 22 | 23 | def get_cpu_id(self): 24 | pass 25 | -------------------------------------------------------------------------------- /images/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunchang272/python-license/fcdddcb7714b3ee514d36a6afbdab92c31779c50/images/frame.png -------------------------------------------------------------------------------- /server/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunchang272/python-license/fcdddcb7714b3ee514d36a6afbdab92c31779c50/server/assets/logo.png -------------------------------------------------------------------------------- /server/data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.aes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunchang272/python-license/fcdddcb7714b3ee514d36a6afbdab92c31779c50/server/data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.aes -------------------------------------------------------------------------------- /server/data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.ori: -------------------------------------------------------------------------------- 1 | eTdxd3o2cHArX3ZednZxeA== 2 | -------------------------------------------------------------------------------- /server/data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAl5YPqnyloWZB+E4aNVZEIz/Ouxw1DLehh2uJanvi8WY//j0i 3 | bq3m9RiTN/BEMJKt28nK2P6nsIyMrH7xcVWJ4dZZ+lha6ExHTXgp6duPyNGtJna3 4 | s9FFduW4+xRea9ZDDEqae2ow2cSM2HD61R62ag163Uj6L1+qnM7lBOXTB8aB7Veh 5 | f610LZfn2PHlAzQs6KGpZGQhsNAFAE0I67LbkNcMYxnLJcghIs60HU+/hZY5uYHc 6 | 7ydBYgRYKrFujOWs6EmM+KapdufixCWlPvswoQOqMppJHVzF4b9XE1XEVG4tAuel 7 | go/fN9LYju+SdC6cMLBvfbDhcPhzUDh//hYk2QIDAQABAoIBAEfctw6OywKp9Kpf 8 | dGNGw0Smsj3YEHo379bAL0QQ+TSR0dKYbuNjg/M7tEc3m0wfrVZd4tFeYGb9Ko94 9 | GsQs1hH/SWa6TlaEGuVqCwy3rwF4DYsHiu0vT6wfGcPGE0EyMVT5ZSUO4ZvLZy4Q 10 | 2YkBZ179hvqy5xBgXQS7vKzGu3Wadhja1QPbK7iui/MQjK1LFzj2YCTL8MkEusHZ 11 | RKnCmGm7fS/DibujylvEMvbtmf0VRFG4vICI84Q2voLoA9w/adqlcmOka/7bDN9k 12 | C7q7wvqtpTIOXPbnIfgsZkib8MDYIanRqnhgW+nThLWnYrD2/P5FCjbaj3Q/lkAk 13 | n/2OtZUCgYEAwKy+23Gvjt1Swe1RTa3+a9T1y72b+/OG80jMQtDuZLx1IWaJi55y 14 | RlYhaGp4YBWWWHCcIz4RLuJWkYdKhdOjygv/XDW20LlTePvBJmls3eQp8qT0G8M/ 15 | NbPmYTsfUbdO5oXikjF06oW743byge6jypbYsxonR/Uhlt4qK5CE6NsCgYEAyWg0 16 | qX13KcuM9/ZiYAd0H+pm4clSckdOCAfGMpJygqz25qq4dlAoaaFsCg9V9ds0WOaj 17 | YJOu4sGk1o1U6D5cEBzSIyxmEKDY8ftW5h73xPHkhTPsExb+b0ApT3OWWujrm6vJ 18 | KXs4TaiPnZHnGDb0VCpeAacfH4/nFtnWGc8gzVsCgYBWYx2PtoFIRON+H7F8aq7Q 19 | GkO6feEaLi4rllA/BP5eHEFIIHk7KGeEMcn4zwP50LQAIz1eb820f7bguRAowKOH 20 | S6nK5ip5mx5Z+Ea5ujsdoddM+7iXWgtVQLvijGeWc+HkadMUSShqnluHuSUyLJUz 21 | mD7AztEd6kKNKj8uGTX+UQKBgFN9Ne9YijAyJ9KWOPtzu4iLY3CPQvUKZ+deSaOn 22 | H37Z8lN2S5cH7Wi0OJQG/Ww4HQAJQ4qCTcnD2e/ykFLhnsozGEzqV7jm5GeS99fW 23 | 2IC0gTVSa7cSHhDB+R3tXVN8IEbctDvUz2cpy7bWx4Q7K5dEJMnsnDM6NOI9FMqI 24 | GSHnAoGAPvyqwOwTIke5Z7E5iw1OZSdjBaNJgeDP8u02WliAQU5iLVwRUtUKRnmz 25 | yCewzRT+JcyUl1bbNY31AA6CSU5T5afdGWvAVixfM7Vhq/A90tDFFLaa7XWq6UMu 26 | 5vryUq0vRRxSGbWLvUGa6YR8+jPJ4RgRnyWS+H3G5hYKRsBFYQY= 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /server/data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl5YPqnyloWZB+E4aNVZE 3 | Iz/Ouxw1DLehh2uJanvi8WY//j0ibq3m9RiTN/BEMJKt28nK2P6nsIyMrH7xcVWJ 4 | 4dZZ+lha6ExHTXgp6duPyNGtJna3s9FFduW4+xRea9ZDDEqae2ow2cSM2HD61R62 5 | ag163Uj6L1+qnM7lBOXTB8aB7Vehf610LZfn2PHlAzQs6KGpZGQhsNAFAE0I67Lb 6 | kNcMYxnLJcghIs60HU+/hZY5uYHc7ydBYgRYKrFujOWs6EmM+KapdufixCWlPvsw 7 | oQOqMppJHVzF4b9XE1XEVG4tAuelgo/fN9LYju+SdC6cMLBvfbDhcPhzUDh//hYk 8 | 2QIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /server/data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/License.lic: -------------------------------------------------------------------------------- 1 | iEfKUnYZHmYI1xEOgwNl/AK56vH7mfJcqOON5I04HXcfwphWT+9V9MsyvlRoyP/EnvX5gTw44hvi 2 | 9+sEdo9j3g== -------------------------------------------------------------------------------- /server/func.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: func.py 6 | Author: sunch 7 | Create Date: 2021/11/12 13:53 8 | ------------------------------------------------- 9 | """ 10 | from Crypto.Cipher import PKCS1_OAEP, AES 11 | from Crypto.PublicKey import RSA 12 | from uuid import uuid4 13 | import datetime 14 | import hashlib 15 | import random 16 | import base64 17 | import time 18 | import os 19 | 20 | 21 | data_folder = './data/' 22 | 23 | 24 | class Utils: 25 | @staticmethod 26 | def rand_key(texture='', allowed_chars=None, length=16): 27 | """ 28 | :param texture: Random key name 29 | :param allowed_chars: Key's dictionary 30 | :param length: Key's length 31 | :return: Random key 32 | """ 33 | secret_key = hashlib.md5(bytes(''.join([texture]), encoding='UTF-8')).hexdigest() 34 | if secret_key is None: 35 | secret_key = "n&^-9#k*-6pwzsjt-qsc@s3$l46k(7e%f80e7gx^f#vouf3yvz" 36 | if allowed_chars is None: 37 | allowed_chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' 38 | random.seed( 39 | hashlib.sha256( 40 | ('%s%s%s' % ( 41 | random.getstate(), 42 | time.time(), 43 | secret_key)).encode('utf-8') 44 | ).digest()) 45 | ret = ''.join(random.choice(allowed_chars) for i in range(length)) 46 | return texture, ret 47 | 48 | @staticmethod 49 | def sha256_enc(chars): 50 | sha256 = hashlib.sha256() 51 | sha256.update(chars) 52 | return sha256.digest() 53 | 54 | @staticmethod 55 | def md5_enc(chars): 56 | md5 = hashlib.md5() 57 | md5.update(chars) 58 | return md5.digest() 59 | 60 | @staticmethod 61 | def base64_enc(chars): 62 | return base64.encodebytes(chars).strip(b'\n') 63 | 64 | @staticmethod 65 | def base64_decode(chars): 66 | return base64.decodebytes(chars) 67 | 68 | @staticmethod 69 | def rsa_enc(chars, key): 70 | # Instantiating PKCS1_OAEP object with the public key for encryption 71 | aes = PKCS1_OAEP.new(key=key) 72 | # Encrypting the message with the PKCS1_OAEP object 73 | return aes.encrypt(chars) 74 | 75 | @staticmethod 76 | def aes_enc(chars, key, mode=AES.MODE_ECB): 77 | # Init AES encryptor 78 | aes = AES.new(key, mode) 79 | # Encrypting the message 80 | return aes.encrypt(chars) 81 | 82 | @staticmethod 83 | def format_license(ori_lic, date, remark): 84 | ori_lic_str = ori_lic.decode().strip('\n') 85 | return '{0}\n{1}\n{2}'.format(ori_lic_str, date, remark).encode() 86 | 87 | @staticmethod 88 | def add_to_16(chars): 89 | while len(chars) % 16 != 0: 90 | chars += b'\0' 91 | return chars 92 | 93 | 94 | class GenKey: 95 | def __init__(self, key_name, key_value): 96 | self.key_name = key_name 97 | self.key_value = key_value 98 | 99 | @staticmethod 100 | def rand_key(): 101 | key_name = str(uuid4()) 102 | return Utils.rand_key(key_name) 103 | 104 | def save_keys(self, pri_key, pub_key, aes_key, ori_key): 105 | """ 106 | :param pri_key: RSA private key 107 | :param pub_key: RSA public key 108 | :param aes_key: AES key after RSA 109 | :param ori_key: AES key before RSA 110 | :return: save result 111 | """ 112 | # Save RSA keys 113 | # Writing down the private and public keys to 'pem' files 114 | date = datetime.datetime.now().strftime('%Y%m%d') 115 | save_path = os.path.join(data_folder, date).replace('\\', '/') 116 | os.makedirs(save_path, exist_ok=True) 117 | key_path = os.path.join(save_path, self.key_name).replace('\\', '/') 118 | if os.path.exists(key_path): 119 | return 'Key name exists' 120 | os.makedirs(key_path) 121 | private_key_name = '{0}_{1}_{2}.{3}'.format(date, self.key_name, 'private', 'pem') 122 | private_key_file = os.path.join(key_path, private_key_name).replace('\\', '/') 123 | public_key_name = '{0}_{1}_{2}.{3}'.format(date, self.key_name, 'public', 'pem') 124 | public_key_file = os.path.join(key_path, public_key_name).replace('\\', '/') 125 | with open(private_key_file, 'wb') as pr_writer: 126 | pr_writer.write(pri_key) 127 | with open(public_key_file, 'wb') as pu_writer: 128 | pu_writer.write(pub_key) 129 | 130 | # Save AES keys 131 | aes_key_name = '{0}_{1}_{2}.{3}'.format(date, self.key_name, 'key', 'aes') 132 | ori_key_name = '{0}_{1}_{2}.{3}'.format(date, self.key_name, 'key', 'ori') 133 | aes_key_path = os.path.join(key_path, aes_key_name) 134 | ori_key_path = os.path.join(key_path, ori_key_name) 135 | with open(aes_key_path, 'wb') as ak_writer: 136 | ak_writer.write(aes_key) 137 | with open(ori_key_path, 'wb') as ok_writer: 138 | ok_writer.write(ori_key) 139 | return 'Success, keys in {0}'.format(key_path) 140 | 141 | def gen_keys(self): 142 | # Generating private key (RsaKey object) of key length of 2048 bits 143 | private_key = RSA.generate(2048) 144 | # Generating the public key (RsaKey object) from the private key 145 | public_key = private_key.publickey() 146 | # Converting the RsaKey objects to string 147 | private_key_export = private_key.export_key() 148 | public_key_export = public_key.export_key() 149 | # Encrypt aes key 150 | # Str to bytes 151 | key_value = self.key_value.encode() 152 | # Base64 encode aes key 153 | base64_key = Utils.base64_enc(key_value) 154 | aes_key = Utils.rsa_enc(base64_key, public_key) 155 | return self.save_keys(private_key_export, public_key_export, aes_key, base64_key) 156 | 157 | 158 | class EncLic: 159 | def __init__(self, key_file, lic_file, due_time, remark): 160 | self.key_file = key_file 161 | self.lic_file = lic_file 162 | self.due_time = due_time 163 | self.remark = remark 164 | self.lic = self.load_lic() 165 | self.key = self.load_key() 166 | 167 | def load_lic(self): 168 | if not os.path.exists(self.lic_file): 169 | return None 170 | return open(self.lic_file, 'rb').read() 171 | 172 | def load_key(self): 173 | if not os.path.exists(self.key_file): 174 | return None 175 | key = open(self.key_file, 'rb').read() 176 | key = Utils.base64_decode(key) 177 | if len(key) > 16: 178 | return None 179 | return key 180 | 181 | def enc_lic(self): 182 | if self.lic is None or self.key is None: 183 | return 'License file or key file invalid' 184 | ori_key = Utils.add_to_16(self.key) 185 | ori_lic = Utils.add_to_16(Utils.format_license(self.lic, self.due_time, self.remark)) 186 | aes_lic = Utils.aes_enc(ori_lic, ori_key) 187 | base64_aes_lic = Utils.base64_enc(aes_lic) 188 | out_lic_file = '{}.enc'.format(os.path.splitext(self.lic_file)[0]) 189 | with open(out_lic_file, 'wb') as lic_writer: 190 | lic_writer.write(base64_aes_lic) 191 | return 'Success, license in {0}'.format(os.path.dirname(self.lic_file)) 192 | 193 | 194 | if __name__ == '__main__': 195 | # key_gen = GenKey('2222', '111') 196 | # sss = key_gen.gen_keys() 197 | # print(sss) 198 | lic_enc = EncLic( 199 | 'data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/20211112_7ac36daa-6000-441c-a23a-098ba0d2df34_key.ori' 200 | , './data/20211112/7ac36daa-6000-441c-a23a-098ba0d2df34/License.lic' 201 | , '2021-11-13 13:11:20') 202 | print(lic_enc.lic) 203 | print(lic_enc.key) 204 | print(lic_enc.enc_lic()) 205 | -------------------------------------------------------------------------------- /server/gui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: gui.py 6 | Author: sunch 7 | Create Date: 2021/11/10 10:36 8 | ------------------------------------------------- 9 | """ 10 | 11 | from tkinter import Tk, Toplevel, Label, Entry, Button, Menu, PhotoImage, StringVar, filedialog 12 | from func import GenKey, EncLic, data_folder 13 | import datetime 14 | 15 | 16 | class NewKeyGUI: 17 | def __init__(self, frame): 18 | self.frame = frame 19 | self.key_name = None 20 | self.key_value = None 21 | self.gen_log = None 22 | self.key_name_label = None 23 | self.key_value_label = None 24 | self.gen_log_label = None 25 | self.help_text_label = None 26 | self.key_name_entry = None 27 | self.key_value_entry = None 28 | self.gen_key_btn = None 29 | self.rand_key_btn = None 30 | 31 | def init_window(self): 32 | self.frame.title('秘钥生成') # 窗口名 33 | self.frame.geometry('520x200+600+100') 34 | self.frame['bg'] = '#F8F8FF' # 窗口背景色,其他背景色见: blog.csdn.net/chl0000/article/details/7657887 35 | self.frame.attributes('-alpha', 0.95) # 虚化,值越小虚化程度越高 36 | 37 | # StringVar 38 | self.key_name = StringVar() 39 | self.key_value = StringVar() 40 | self.gen_log = StringVar() 41 | 42 | # Label 43 | self.key_name_label = Label(self.frame, text='秘钥标识:', bg='#F8F8FF', font=('YaHei', 10)) 44 | self.key_name_label.grid(row=0, column=0, pady=10, padx=10) 45 | self.key_value_label = Label(self.frame, text='AES秘钥:', bg='#F8F8FF', font=('YaHei', 10)) 46 | self.key_value_label.grid(row=1, column=0, pady=10, padx=10) 47 | self.gen_log_label = Label(self.frame, textvariable=self.gen_log, bg='#F8F8FF', font=('YaHei', 10)) 48 | self.gen_log_label.grid(row=3, column=0, columnspan=2, pady=10, padx=5, sticky='W') 49 | self.help_text_label = Label(self.frame, text='帮 助:TODO', bg='#F8F8FF', font=('YaHei', 10)) 50 | self.help_text_label.grid(row=4, column=0, columnspan=2, pady=10, padx=10, sticky='W') 51 | 52 | # Entry 53 | self.key_name_entry = Entry(self.frame, width=50, bd=1, textvariable=self.key_name, font=('YaHei', 10)) 54 | self.key_name_entry.grid(row=0, column=1, pady=10, padx=5) 55 | self.key_value_entry = Entry(self.frame, width=50, bd=1, textvariable=self.key_value, font=('YaHei', 10)) 56 | self.key_value_entry.grid(row=1, column=1, pady=10, padx=5) 57 | 58 | # Button 59 | self.gen_key_btn = Button(self.frame, text='生成秘钥', command=self.gen_key, bd=1, bg='#F8F8FF', width=15, 60 | font=('YaHei', 10)) 61 | self.gen_key_btn.grid(row=2, column=0, pady=5, padx=10) 62 | self.rand_key_btn = Button(self.frame, text='随机生成', command=self.rand_key, bd=1, bg='#F8F8FF', width=15, 63 | font=('YaHei', 10)) 64 | self.rand_key_btn.grid(row=2, column=1, pady=5, padx=5, sticky='W') 65 | 66 | def gen_key(self): 67 | key_gener = GenKey(self.key_name.get(), self.key_value.get()) 68 | self.gen_log.set(key_gener.gen_keys()) 69 | 70 | def rand_key(self): 71 | rand_name, rand_key = GenKey.rand_key() 72 | self.key_name.set(rand_name) 73 | self.key_value.set(rand_key) 74 | # self.gen_key() 75 | 76 | 77 | class EncLicGUI: 78 | def __init__(self, frame): 79 | self.frame = frame 80 | self.aes_key_path = None 81 | self.ori_lic_path = None 82 | self.due_time = None 83 | self.remark = None 84 | self.gen_log = None 85 | self.aes_key_select_btn = None 86 | self.ori_lic_select_btn = None 87 | self.due_time_label = None 88 | self.remark_label = None 89 | self.gen_log_label = None 90 | self.help_text_label = None 91 | self.key_path_entry = None 92 | self.lic_path_entry = None 93 | self.due_time_entry = None 94 | self.remark_entry = None 95 | self.enc_lic_btn = None 96 | 97 | def init_window(self): 98 | self.frame.title('License加密') # 窗口名 99 | self.frame.geometry('520x400+600+100') 100 | self.frame['bg'] = '#F8F8FF' # 窗口背景色,其他背景色见: blog.csdn.net/chl0000/article/details/7657887 101 | self.frame.attributes('-alpha', 0.95) # 虚化,值越小虚化程度越高 102 | 103 | # StringVar 104 | self.aes_key_path = StringVar() 105 | self.ori_lic_path = StringVar() 106 | self.due_time = StringVar() 107 | self.remark = StringVar() 108 | self.gen_log = StringVar() 109 | 110 | # Button 111 | self.aes_key_select_btn = Button(self.frame, text='选择AES秘钥', command=self.get_key_path, bd=1, bg='#F8F8FF', 112 | width=15, font=('YaHei', 10)) 113 | self.aes_key_select_btn.grid(row=0, column=0, pady=10, padx=10) 114 | self.ori_lic_select_btn = Button(self.frame, text='选择License', command=self.get_lic_path, bd=1, bg='#F8F8FF', 115 | width=15, font=('YaHei', 10)) 116 | self.ori_lic_select_btn.grid(row=1, column=0, pady=10, padx=10) 117 | 118 | # Label 119 | self.due_time_label = Label(self.frame, text='输入到期时间:', bg='#F8F8FF', font=('YaHei', 10)) 120 | self.due_time_label.grid(row=2, column=0, pady=10, padx=10, sticky='E') 121 | self.remark_label = Label(self.frame, text='备注:', bg='#F8F8FF', font=('YaHei', 10)) 122 | self.remark_label.grid(row=3, column=0, pady=10, padx=10, sticky='E') 123 | self.gen_log_label = Label(self.frame, textvariable=self.gen_log, bg='#F8F8FF', font=('YaHei', 10)) 124 | self.gen_log_label.grid(row=5, column=0, columnspan=2, pady=10, padx=5, sticky='W') 125 | self.help_text_label = Label(self.frame, text='帮 助:TODO', bg='#F8F8FF', font=('YaHei', 10)) 126 | self.help_text_label.grid(row=6, column=0, columnspan=2, pady=10, padx=10, sticky='W') 127 | 128 | # Entry 129 | self.key_path_entry = Entry(self.frame, textvariable=self.aes_key_path, width=50, font=('YaHei', 10)) 130 | self.key_path_entry.grid(row=0, column=1, pady=10, padx=10) 131 | self.lic_path_entry = Entry(self.frame, textvariable=self.ori_lic_path, width=50, font=('YaHei', 10)) 132 | self.lic_path_entry.grid(row=1, column=1, pady=10, padx=10) 133 | self.due_time_entry = Entry(self.frame, textvariable=self.due_time, width=50, font=('YaHei', 10)) 134 | self.due_time_entry.grid(row=2, column=1, pady=10, padx=10) 135 | self.due_time.set(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 136 | self.remark_entry = Entry(self.frame, textvariable=self.remark, width=50, font=('YaHei', 10)) 137 | self.remark_entry.grid(row=3, column=1, pady=10, padx=10) 138 | 139 | # Button 140 | self.enc_lic_btn = Button(self.frame, text='加密License', command=self.enc_lic, bd=1, bg='#F8F8FF', width=15, 141 | font=('YaHei', 10)) 142 | self.enc_lic_btn.grid(row=4, column=0, pady=10, padx=10) 143 | 144 | def get_key_path(self): 145 | file_path = filedialog.askopenfilename(parent=self.frame, initialdir=data_folder, filetypes=[('ori', '*.ori')]) 146 | self.aes_key_path.set(file_path) 147 | 148 | def get_lic_path(self): 149 | file_path = filedialog.askopenfilename(parent=self.frame, initialdir=data_folder, filetypes=[('ori', '*.ori')]) 150 | self.ori_lic_path.set(file_path) 151 | 152 | def enc_lic(self): 153 | lic_enc = EncLic(self.aes_key_path.get(), self.ori_lic_path.get(), self.due_time.get(), self.remark.get()) 154 | self.gen_log.set(lic_enc.enc_lic()) 155 | 156 | 157 | class AuthMainGUI: 158 | def __init__(self, init_window_name): 159 | self.init_window_name = init_window_name 160 | # Main Menu 161 | self.main_menu = None 162 | self.file_menu = None 163 | self.logo_img = None 164 | self.logo_img_label = None 165 | self.help_text_label = None 166 | self.copyright_text_label = None 167 | # New Key Window 168 | self.new_key_window = None 169 | self.new_key_portal = None 170 | # ENC LIC Window 171 | self.enc_lic_window = None 172 | self.enc_lic_portal = None 173 | 174 | # 设置窗口 175 | def init_window(self): 176 | self.init_window_name.title('注册工具_v1.0') # 窗口名 177 | self.init_window_name.geometry('320x100+500+300') 178 | self.init_window_name['bg'] = '#F8F8FF' # 窗口背景色,其他背景色见: blog.csdn.net/chl0000/article/details/7657887 179 | self.init_window_name.attributes('-alpha', 0.95) # 虚化,值越小虚化程度越高 180 | 181 | # Main Menu Settings 182 | self.main_menu = Menu(self.init_window_name) 183 | self.file_menu = Menu(self.main_menu, tearoff=False) 184 | self.file_menu.add_command(label='New', command=self.display_new_key_frame) 185 | self.file_menu.add_command(label='Enc Lic', command=self.display_enc_lic_frame) 186 | self.file_menu.add_separator() 187 | self.file_menu.add_command(label='Exit', command=self.init_window_name.quit) 188 | self.main_menu.add_cascade(label='File', menu=self.file_menu) 189 | self.init_window_name.config(menu=self.main_menu) 190 | 191 | # Label 192 | self.logo_img = PhotoImage(file='assets/logo.png') 193 | self.logo_img_label = Label(self.init_window_name, image=self.logo_img, bg='#F8F8FF', width=50, height=50) 194 | 195 | self.help_text_label = Label(self.init_window_name, text='TODO', bg='#F8F8FF', font=('YaHei', 10)) 196 | self.copyright_text_label = Label(self.init_window_name, text='TODO', bg='#F8F8FF', underline=0, 197 | font=('YaHei', 8, 'italic')) 198 | self.logo_img_label.grid(row=0, column=1) 199 | self.help_text_label.grid(row=0, column=0, padx=20) 200 | self.copyright_text_label.grid(row=1, column=0, padx=20, pady=20) 201 | 202 | def display_new_key_frame(self): 203 | self.new_key_window = Toplevel() 204 | self.new_key_portal = NewKeyGUI(self.new_key_window) 205 | self.new_key_portal.init_window() 206 | self.new_key_window.mainloop() 207 | 208 | def display_enc_lic_frame(self): 209 | self.enc_lic_window = Toplevel() 210 | self.enc_lic_portal = EncLicGUI(self.enc_lic_window) 211 | self.enc_lic_portal.init_window() 212 | self.enc_lic_window.mainloop() 213 | 214 | 215 | def gui_open(): 216 | # 实例化出一个父窗口 217 | init_window = Tk() 218 | main_portal = AuthMainGUI(init_window) 219 | # 设置根窗口默认属性 220 | main_portal.init_window() 221 | # 父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示 222 | init_window.mainloop() 223 | 224 | 225 | if __name__ == '__main__': 226 | gui_open() 227 | -------------------------------------------------------------------------------- /server/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ------------------------------------------------- 4 | Project Name: python-auth 5 | File Name: main.py 6 | Author: sunch 7 | Create Date: 2021/11/10 14:20 8 | ------------------------------------------------- 9 | """ 10 | 11 | from gui import gui_open 12 | 13 | if __name__ == '__main__': 14 | gui_open() 15 | --------------------------------------------------------------------------------