很多同學(xué)可能開發(fā)了非常多的程序了,并且進行了 exe 的打包,可是由于沒有使用序列號,程序被無限復(fù)制,導(dǎo)致收益下降。
接下來我們來自己實現(xiàn)序列號的生成及使用,通過本文的學(xué)習(xí),希望能夠幫助到你!
本文適合 windows 系統(tǒng),linux 系統(tǒng)原理相通,但代碼有所不同。
安裝庫pip install wmipip install pycryptodome結(jié)構(gòu)流程圖
結(jié)構(gòu)圖
我們首先要通過 硬件信息、UUID和時間戳 來生成一個 協(xié)議文件,再通過非對稱加密 RSA 生成公鑰和私鑰。
當(dāng)我們給客戶程序的時候,會附帶一個 私鑰程序啟動時會判斷是否存在 協(xié)議文件 ,如果沒有 協(xié)議文件 將會生成一個 序列號,客戶需要把序列號發(fā)送給管理員管理員獲取 序列號,使用 公鑰 進行加密生成 協(xié)議文件,將其發(fā)送給客戶客戶將 協(xié)議文件 放在相應(yīng)位置,程序使用 私鑰 進行解密,與相應(yīng)的 序列號 進行對比協(xié)議文件 解密成功,通過 協(xié)議文件序列號 中的時間戳信息判斷是否過期當(dāng)時間戳未過期,則運行程序,反之無法啟動,提示 序列號過期結(jié)構(gòu)代碼1. 生成RSA公鑰與私鑰文件from Crypto import Randomfrom Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_v1_5import osimport datetimeimport base64CURRENT_FOLDER_PATH = os.path.dirname(os.path.abspath(__file__))def make_rsa_key(length=1024): """ 生成公鑰和私鑰 :return: """ # 偽隨機數(shù)生成器 random_gen = Random.new().read # 生成秘鑰對實例對象:1024是秘鑰的長度 rsa = RSA.generate(length, random_gen) private_pem = rsa.exportKey() public_pem = rsa.publickey().exportKey() return private_pem, public_pemdef rsa_encrypt(pub_key, content, length=128): """ rsa數(shù)據(jù)加密,單次加密串的長度最大為 (key_size/8)-11 1024bit的證書用100, 2048bit的證書用 200 :param pub_key: :param content: :param length: :return: """ pub_key = RSA.importKey(pub_key.decode()) cipher = PKCS1_v1_5.new(pub_key) content = content.encode() res = [] for i in range(0, len(content), length): res.append(cipher.encrypt(content[i: i + length])) return base64.b64encode(base64.b64encode(b''.join(res)))def rsa_decrypt(pri_key, encrypt_txt, length=128): """ rsa信息解密 1024bit的證書用128,2048bit證書用256位 :param pri_key: :param encrypt_txt: :param length: :return: """ try: encrypt_txt = base64.b64decode(encrypt_txt) encrypt_txt = base64.b64decode(encrypt_txt.decode()) pri_obj = RSA.importKey(pri_key) pri_obj = PKCS1_v1_5.new(pri_obj) res = [] for i in range(0, len(encrypt_txt), length): res.append(pri_obj.decrypt(encrypt_txt[i:i + length], '')) return b''.join(res) except Exception as e: return Nonedef rsa_file_generator(pri_path, pub_path, length=1024): """ 生成公私鑰文件 :param pri_path: 私鑰文件地址 :param pub_path: 公鑰文件地址 :return: """ pri_key, pub_key = make_rsa_key(length) with open(pri_path, 'wb') as f: f.write(pri_key) with open(pub_path, 'wb') as f: f.write(pub_key) return pri_key, pub_keydef get_or_make_rsa(refresh=False): """ 生成或獲取公私鑰內(nèi)容 :return: """ folder_path = os.path.join(CURRENT_FOLDER_PATH, 'pem') if not os.path.exists(folder_path): os.makedirs(folder_path) file_list = os.listdir(folder_path) now = datetime.datetime.now() now_str = now.strftime('%Y%m%d%H%M%S') new_pri_path = os.path.join(folder_path, f'{now_str}_private.pem') new_pub_path = os.path.join(folder_path, f'{now_str}_public.pem') # 公鑰和私鑰文件不存在 if len(file_list) == 0: pri_key, pub_key = rsa_file_generator( new_pri_path, new_pub_path, 1024 ) else: pri_path = '' pub_path = '' for file in file_list: if file.endswith('private.pem'): pri_path = os.path.join(folder_path, file) elif file.endswith('public.pem'): pub_path = os.path.join(folder_path, file) if not pri_path or not pub_path: pri_key, pub_key = rsa_file_generator( new_pri_path, new_pub_path, 1024 ) else: # 手動更新私鑰 if refresh: pri_key, pub_key = rsa_file_generator( new_pri_path, new_pub_path, 1024 ) os.remove(pri_path) os.remove(pub_path) else: with open(pri_path, 'rb') as f: pri_key = f.read() with open(pub_path, 'rb') as f: pub_key = f.read() return pri_key, pub_key使用 get_or_make_rsa() 方法即可獲取公鑰與私鑰,程序會生成一個 pem 文件夾保存相應(yīng)的公鑰和私鑰。
如果需要更新公鑰與私鑰,只要調(diào)用 get_or_make_rsa() 方法時,傳入 refresh 的值為 True 即可。
CURRENT_FOLDER_PATH 全局變量保存的是當(dāng)前程序的絕對路徑,就算是打包后的 exe 也可以正確獲取。
2. 序列號生成import uuidimport wmiimport hashlibfrom itertools import zip_longestdef get_license_txt(): c = wmi.WMI() # 獲取第一個硬盤的序列號 disk_number = '' for physical_disk in c.Win32_DiskDrive(): disk_number = physical_disk.SerialNumber.strip() break # 獲取第一個CPU的序列號 cpu_number = '' for cpu in c.Win32_Processor(): cpu_number = cpu.ProcessorId.strip() break # 獲取第一個BIOS的序列號 bios_number = '' for bios in c.Win32_BIOS(): bios_number = bios.SerialNumber.strip() break uid = get_a_guid() paired = zip_longest(disk_number, cpu_number, bios_number, uid, fillvalue='_') result = '|'.join([''.join(pair) for pair in paired]) content = hashlib.md5(result.encode()).hexdigest().upper() return f'{content}=={uid}'我們使用了一個相對復(fù)雜的方式將 硬盤、cpu、BIOS 的序列號與一個 UUID 進行組合,竟將其進行 md5 加密,最后將這個 加密結(jié)果 與 UUID 明文組合在一起作為一個 完整的軟件序列號。
現(xiàn)在,客戶將在程序啟動時拿到這個 軟件序列號 ,再將這個軟件序列號發(fā)送給管理員進行加密即可。
加密方法將使用之前的 rsa_encrypt() 方法。
3. 加密軟件序列號def make_license_key_file(license_txt='', days=365): ''' 創(chuàng)建協(xié)議文件 :param license_txt: 軟件序列號 :param days: 有效天數(shù) :return: ''' pri_key, pub_key = get_or_make_rsa() s_list = license_txt.split('==') if len(s_list) != 2: return None uid = s_list[1] timestamp = int(datetime.datetime.now().timestamp()) + days * 86400 new_license_text = f'{license_txt}=={timestamp}' res = rsa_encrypt(pub_key, new_license_text).decode() folder_path = os.path.join(CURRENT_FOLDER_PATH, 'key') if not os.path.exists(folder_path): os.makedirs(folder_path) file_path = os.path.join(folder_path, f'{uid}.key') with open(file_path, 'wb') as f: f.write(res.encode()) return file_path以當(dāng)前時間戳為基準(zhǔn)添加有效天數(shù),然后將客戶發(fā)送過來的序列號再進行一次組合,最后通過 公鑰 進行加密,生成一個 協(xié)議文件 發(fā)送給客戶。
4. 驗證協(xié)議文件def auth_license(key_file_path='', pem_file_path=''): c = wmi.WMI() disk_number = '' for physical_disk in c.Win32_DiskDrive(): disk_number = physical_disk.SerialNumber.strip() break cpu_number = '' for cpu in c.Win32_Processor(): cpu_number = cpu.ProcessorId.strip() break bios_number = '' for bios in c.Win32_BIOS(): bios_number = bios.SerialNumber.strip() break # 協(xié)議文件不存在,無法通過 if not os.path.exists(key_file_path): return False # 私鑰文件不存在,無法通過 if not os.path.exists(pem_file_path): return False # 讀取協(xié)議文件內(nèi)容 with open(key_file_path, 'rb') as f: key_res = f.read().decode() # 讀取私鑰內(nèi)容 with open(pem_file_path, 'rb') as f: pem_res = f.read().decode() res = rsa_decrypt(pem_res, key_res).decode() # 解密失敗,無法通過 if not res: return False s_list = res.split('==') # 沒有兩個等號的內(nèi)容,無法通過 if len(s_list) != 3: return False uid = s_list[1] paired = zip_longest(disk_number, cpu_number, bios_number, uid, fillvalue='_') result = '|'.join([''.join(pair) for pair in paired]) content = hashlib.md5(result.encode()).hexdigest().upper() # 序列號不一致,無法通過 if content != s_list[0]: return False try: timestamp = int(s_list[2]) except Exception as e: # 無法變?yōu)闀r間戳,無法通過 return False now_timestamp = int(datetime.datetime.now().timestamp()) # 在有效時間內(nèi),通過 if now_timestamp <= timestamp: return True return False這個驗證過程其實就是再次進行一次 序列號 組合,來判斷是否與 協(xié)議文件 一致,其后還需判斷是否在有效期內(nèi)。
結(jié)尾我相信如果認(rèn)真看完文章的朋友已經(jīng)可以實現(xiàn)自己的序列號生成器了,不過在此之前我需要申明這個文章的代碼還不是完整版,這里提幾個優(yōu)化點:
定時驗證:定時判斷是否超過有效期,防止客戶程序未關(guān)閉,就算超過有效期還能繼續(xù)使用 打包為加密模塊:當(dāng)前代碼為明文代碼,由于 python 為解釋器語言,如果不加密打包,很容易被破解規(guī)則 添加可視化窗口:為了方便使用,可以將程序設(shè)計為可視化窗口模式,最簡單的方式使用 TK 創(chuàng)建當(dāng)然,如果你不想自己寫,推薦可以直接使用 pyarmor ,這也是國人開發(fā)的一個加密庫,包含加密、有效期、許可證。
如果這篇文章對你有幫助,點個贊讓我知道哦!
轉(zhuǎn)載請注明來自夕逆IT,本文標(biāo)題:《win10密鑰生成器(為你的python程序上鎖軟件序列號生成器)》

還沒有評論,來說兩句吧...