This article is participating in Python Theme Month. See the link to the event for more details

1 Requirement Description

When the project platform was first deployed on the server, the system was not authorized.

If you want to deploy the platform to a specific server, you need to provide the MAC address of the server and the authorization expiration time to obtain the authorization code. After receiving the authorization code, you can use the migration platform.

Upon receiving the authorization request, the licensor obtains the MAC address of the target server installed on the platform. A License is generated based on an algorithm for binding MAC addresses, and the expiration time of the License is specified. The generated License is compared with the information generated by the built-in algorithm in the software. If the information is matched, the License is granted successfully. If the comparison fails or the authorization expires, the authorization fails.

2 Authorization process

2.1 Generating the Authorization process

2.2 Authentication authorization Process

3 Code implementation

3.1 Obtaining a Mac Address

def get_mac_address(self) :
	mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
	return ":".join([mac[e:e + 2] for e in range(0.11.2)])
Copy the code

3.2 Encrypting Mac Addresses

The core of the algorithm is to hash MAC addresses. To increase the difficulty of the generated license file, add a specific character before the MAC address to make it slightly harder to crack the license generation software. For example, in the sample code here, certain characters are tentatively named SmartAnt.

The Hash algorithm is designed to be unsolvable. To put it simply is simple, reverse difficult.

 # 1. Obtain the key and use the hash algorithm to calculate the MAC address of the target computer
 	psw = self.hash_msg('smartant' + str(mac_addr))
 Create a new dictionary license_str to store the real MAC address, license expiration time, and encrypted character string
 	license_str = {}
 	license_str['mac'] = mac_addr
 	license_str['time_str'] = end_date
	license_str['psw'] = psw
Copy the code

The generated lincense_str is used as a dictionary and cannot be output as a License, because its components and results can be directly seen. Therefore, to further encrypt the generated License information, ensure that it is a disordered and meaningless string, use AEScoder to encrypt. This encapsulates an AES encrypted class

3.3 the AES encryption

""" AES encryption and decrypting tool class data block 128-bit key for 16-bit character set UTF-8 Output for Base64 AES Encryption mode pkCS7PADDING for CBC

import base64
from Crypto.Cipher import AES
from django.conf import settings


class AESHelper(object) :
    def __init__(self, password, iv) :
        self.password = bytes(password, encoding='utf-8')
        self.iv = bytes(iv, encoding='utf-8')

    def pkcs7padding(self, text) :
        """ """ """ """ """ """ """ """ ""
        bs = AES.block_size  # 16
        length = len(text)
        bytes_length = len(bytes(text, encoding='utf-8'))
        For UTF-8 encoding, English takes 1 byte and Chinese takes 3 bytes
        padding_size = length if(bytes_length == length) else bytes_length
        padding = bs - padding_size % bs
        # tips: CHR (padding) Look at conventions with other languages, some use '\0'
        padding_text = chr(padding) * padding
        return text + padding_text

    def pkcs7unpadding(self, text) :
        Param text: decrypted string :return: """
        length = len(text)
        unpadding = ord(text[length-1])
        return text[0:length-unpadding]

    def encrypt(self, content) :
        """ AES encryption mode CBC fill PKCS7 :param key: key: param Content: encryption content: return: """
        cipher = AES.new(self.password, AES.MODE_CBC, self.iv)
        content_padding = self.pkcs7padding(content)
        encrypt_bytes = cipher.encrypt(bytes(content_padding, encoding='utf-8'))
        result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
        return result

    def decrypt(self, content) :
        """ AES decryption mode CBC to fill pkCS7 :param Key: :param Content: :return: """
        cipher = AES.new(self.password, AES.MODE_CBC, self.iv)
        encrypt_bytes = base64.b64decode(content)
        decrypt_bytes = cipher.decrypt(encrypt_bytes)
        result = str(decrypt_bytes, encoding='utf-8')
        result = self.pkcs7unpadding(result)
        return result


def get_aes() :
    # AES_SECRET and AES_IV are keys and offsets, respectively
    aes_helper = AESHelper(settings.AES_SECRET, settings.AES_IV)
    return aes_helper
Copy the code

Generating a License Code

def generate_license(self, end_date, mac_addr) :
	print("Received end_date: {}, mac_addr: {}".format(end_date, mac_addr))
	psw = self.hash_msg('smartant' + str(mac_addr))
	license_str = {}
	license_str['mac'] = mac_addr
	license_str['time_str'] = end_date
	license_str['psw'] = psw
	s = str(license_str)
	licence_result = get_aes().encrypt(s)
	return licence_result
Copy the code

3.4 Final Code

import uuid
import hashlib
import datetime
from common.aes_encrypt import get_aes
class LicenseHelper(object) :
    def generate_license(self, end_date, mac_addr) :
        print("Received end_date: {}, mac_addr: {}".format(end_date, mac_addr))
        psw = self.hash_msg('smartant' + str(mac_addr))
        license_str = {}
        license_str['mac'] = mac_addr
        license_str['time_str'] = end_date
        license_str['psw'] = psw
        s = str(license_str)
        licence_result = get_aes().encrypt(s)
        return licence_result
        
    def get_mac_address(self) :
        mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
        return ":".join([mac[e:e + 2] for e in range(0.11.2)])
        
    def hash_msg(self, msg) :
        sha256 = hashlib.sha256()
        sha256.update(msg.encode('utf-8'))
        res = sha256.hexdigest()
        return res
        
    def read_license(self, license_result) :
        lic_msg = bytes(license_result, encoding="utf8")
        license_str = get_aes().decrypt(lic_msg)
        license_dic = eval(license_str)
        return license_dic
        
    def check_license_date(self, lic_date) :
        current_time = datetime.datetime.strftime(datetime.datetime.now() ,"%Y-%m-%d %H:%M:%S")
        current_time_array = datetime.datetime.strptime(current_time,"%Y-%m-%d %H:%M:%S")
        lic_date_array = datetime.datetime.strptime(lic_date, "%Y-%m-%d %H:%M:%S")
        remain_days = lic_date_array - current_time_array
        remain_days = remain_days.days
        if remain_days < 0 or remain_days == 0:
            return False
        else:
            return True
            
    def check_license_psw(self, psw) :
        mac_addr = self.get_mac_address()
        hashed_msg = self.hash_msg('smartant' + str(mac_addr))
        if psw == hashed_msg:
            return True
        else:
            return False
Copy the code
oper = LicenseHelper()
read_bool, license_dic = oper.read_license(license)
if not read_bool:
	res['status'] = False
	res['msg'] = "Read failed, invalid License, error message: {}".format(license_dic)
	return Response(res, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
date_bool = oper.check_license_date(license_dic['time_str'])
psw_bool = oper.check_license_psw(license_dic['psw'])
if psw_bool:
	if date_bool:
		res['status'] = True
		res['time'] = license_dic['time_str']
		res['msg'] = ""
	else:
		res['status'] = False
		res['time'] = license_dic['time_str']
		res['msg'] = "Activation Code expired"
else:
	res['status'] = False
	res['time'] = license_dic['time_str']
	res['msg'] = "MAC does not match, the License is invalid, please replace the License."
if psw_bool and date_bool:
	serializer_content = {
		"license": license,
		"end_time": license_dic['time_str']
	}
	license_serializer.save(**serializer_content)
	return Response(res, status=status.HTTP_200_OK)
else:
	return Response(res, status=status.HTTP_422_UNPROCESSABLE_ENTITY)
Copy the code

4 Operation Result

4.1 Normal Activation

4.2 expired

4.3 Incorrect MAC (code not running on authorized machine)

5 a Shell script

#! /bin/bash

# Defining variables
create_license="./libs/create.py"
show_mac="./libs/showmac.py"

function echo_green(){
   echo -e "\033[32m$1\033[0m $2"
}
function echo_red(){
   echo -e "\033[31m$1\033[0m $2"
}
function echo_yellow(){
   echo -e "\033[33m$1\033[0m $2"
}
function echo_blue(){
   echo -e "\033[34m$1\033[0m $2"
}

# Step1 Check Python Environomentfunction env_check(){ clear echo_blue "[Step1]" "Python env check dependencies packages... plz wait." pip3 list --format=columns | grep pycrypto &>/dev/null python_env=$? if [[ ${python_env} -eq 0 ]]; then echo_green "[Step1]" "Done" else yum install -y gcc gcc-c++ python36 python36-pip python36-devel && clear pip3 install pycrypto -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com && clear if [[ $? -eq 0 ]]; then echo_blue "[Step1]" "Python env check dependencies packages... plz wait." echo_green "[Step1]" "Done" else echo_red "[Error]" "Python config error" && exit 1 fi fi }
# Step2 Input EndTime and MAC, Create Licensefunction generate_license(){ while true do echo_blue "[Step2] Please enter the expiration time of the license: (eg: 2021-04-05 12:00:00)" && read end_time if [ -n "${end_time}" ]; then if date +"%d-%b-%y %H:%M:%S" -d "${end_time}" >> /dev/null 2>&1; then echo_green "[Step2]" "Date Provided by user : ${end_time}" break else echo_red "[Error]" "Wrong date format please input correct format like: 2021-04-05 12:00:00" fi fi done while true do echo_blue "[Step2] Please enter the MAC address of the server: (eg: 52:54:f5:a7:dc:4c)" && read mac if [ -n "${mac}" ]; then break fi done echo_yellow "[Step2] The expiraion time is: ${end_time}, MAC is: ${mac}" if [ -n "${end_time}" ] && [ -n "${mac}" ]; then license=`python3 ${create_license} -t "${end_time}" -m "${mac}"` echo_blue "[Finished] Generate License Success:" echo_green ${license} else echo_red "[Error] Create license failed." exit 1 fi }
# Show mac address
function show_mac(){
  mac_address=`python ${show_mac}`
  echo_yellow ${mac_address}
}

# Show usage
function show_usage(){
  echo "Usage:"
  echo "     $0 [command]"
  echo "Available Commands:"
  echo "  -c|create       Create a license for smartant platform."
  echo "  -s|showmac      Show mac address for linux server."
}
# Function mainif [ $# -eq 1 ]; then case $1 in -c|create) env_check generate_license ;; -s|showmac) show_mac ;; *) show_usage exit 1 esac else show_usage exit 1 fiCopy the code

Viewing usage Instructions

Obtaining a MAC address

To generate the License