一种license文件生成方案


前言

有时,我们为了保护自己写的程序不被滥用(搞点钱),会想绑定硬件,还要设置使用期限。本文就实践了一种license方案,当然没有考虑逆向,这个方面也不在讨论的范畴,所以大家要保护自己的代码时请充分调研!!!


一、总体方案设计

  1. 获取硬件码,这个可以是CPU序号、MAC地址等这些基本不会变的东西。
  2. 设定截止日期等信息,把你要校验的信息(包括硬件码)一起做一个加密,例如AES加密(有人可能会问都加密了,不是已经好了吗,后面再解释)。
  3. 把上面加密后的校验信息我们做一个MD5验签,就是把校验信息计算一个MD5摘要,然后用RSA加密,检验校验信息是否是伪造的。
  4. 我们用base64编码一下上面这些信息,让它保存成文本写入文件。
  5. 验证文件,首先AES解密校验信息,然后使用RSA解密MD5签名,查看校验信息的MD5是否和RSA解密得到的一致,然后再去判断校验信息里面的日期等等。

整个数据格式

[aes随机密码][数据原始长度][aes加密数据][md5签名]

二、硬件码获取

我选择了使用CPU序列号,这方面可以用别的编码,看个人选择,代码是抄来修改的,跨windows和linux。

#include "hardwarecode.h"

namespace {

#ifdef __linux__
	// reference: https://stackoverflow.com/questions/6491566/getting-the-machine-serial-number-and-cpu-id-using-c-c-in-linux
	inline void native_cpuid(unsigned int* eax, unsigned int* ebx, unsigned int* ecx, unsigned int* edx)
	{
		// ecx is often an input as well as an output
		asm volatile("cpuid"
			: "=a" (*eax),
			"=b" (*ebx),
			"=c" (*ecx),
			"=d" (*edx)
			: "0" (*eax), "2" (*ecx));
	}
#endif

} // namespace
#include <array>
#include <vector>
#ifdef _MSC_VER
#include <stdio.h>
#include <Windows.h>
#include <Iphlpapi.h>
#include <Assert.h>
#include <intrin.h >
#pragma comment(lib, "iphlpapi.lib")
#endif // _MSC_VER

int get_hardware_code(std::string& str)
{
	str.clear();
	// get mac
#ifdef _MSC_VER
	// reference: https://stackoverflow.com/questions/13646621/how-to-get-mac-address-in-windows-with-c
	PIP_ADAPTER_INFO AdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
	if (AdapterInfo == nullptr) {
		fprintf(stderr, "fail to malloc\n");
		return -1;
	}

	DWORD dwBufLen = sizeof(IP_ADAPTER_INFO);
	char mac_addr[255] = {0};

	// Make an initial call to GetAdaptersInfo to get the necessary size into the dwBufLen variable
	if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_BUFFER_OVERFLOW) {
		free(AdapterInfo);
		AdapterInfo = (IP_ADAPTER_INFO*)malloc(dwBufLen);
		if (AdapterInfo == nullptr) {
			fprintf(stderr, "fail to malloc\n");
			return -1;
		}
	}

	if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == NO_ERROR) {
		for (PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo; pAdapterInfo != nullptr; pAdapterInfo = pAdapterInfo->Next) {
			// technically should look at pAdapterInfo->AddressLength and not assume it is 6.
			if (pAdapterInfo->AddressLength != 6) continue;
			if (pAdapterInfo->Type != MIB_IF_TYPE_ETHERNET) continue;

			sprintf(mac_addr, "%02X:%02X:%02X:%02X:%02X:%02X",
				pAdapterInfo->Address[0], pAdapterInfo->Address[1],
				pAdapterInfo->Address[2], pAdapterInfo->Address[3],
				pAdapterInfo->Address[4], pAdapterInfo->Address[5]);
			//fprintf(stdout, "mac address: %s\n", mac_addr);
			str += mac_addr;
			break;
		}
	}
	free(AdapterInfo);
#else
	// reference: https://stackoverflow.com/questions/1779715/how-to-get-mac-address-of-your-machine-using-a-c-program/35242525
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		fprintf(stderr, "fail to socket: %d\n", sock);
		return -1;
	};

	struct ifconf ifc;
	char buf[1024];
	int success = 0;

	ifc.ifc_len = sizeof(buf);
	ifc.ifc_buf = buf;
	if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) {
		fprintf(stderr, "fail to ioctl: SIOCGIFCONF\n");
		return -1;
	}

	struct ifreq* it = ifc.ifc_req;
	const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
	struct ifreq ifr;

	for (; it != end; ++it) {
		strcpy(ifr.ifr_name, it->ifr_name);
		if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
			if (!(ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
				if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
					success = 1;
					break;
				}
			}
		}
		else {
			fprintf(stderr, "fail to ioctl: SIOCGIFFLAGS\n");
			return -1;
		}
	}

	unsigned char mac_address[6];
	if (success) memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
	//fprintf(stdout, "mac address: %02X:%02X:%02X:%02X:%02X:%02X\n", mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]);
	sprintf(mac_addr, "%02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]);
#endif

	// Capture vendor string
	char vendor[0x20];
	memset(vendor, 0, sizeof(vendor));

	// get cpid
#ifdef _MSC_VER
	// reference: https://docs.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=vs-2019
	std::array<int, 4> cpui;
	// Calling __cpuid with 0x0 as the function_id argument gets the number of the highest valid function ID
	__cpuid(cpui.data(), 0);
	int nIds_ = cpui[0];

	std::vector<std::array<int, 4>> data_;
	for (int i = 0; i <= nIds_; ++i) {
		__cpuidex(cpui.data(), i, 0);
		data_.push_back(cpui);

		//fprintf(stdout, "%08X-%08X-%08X-%08X\n", cpui[0], cpui[1], cpui[2], cpui[3]);
	}
	char str_buff[1024] = { 0 };
	*reinterpret_cast<int*>(vendor) = data_[0][1];
	*reinterpret_cast<int*>(vendor + 4) = data_[0][3];
	*reinterpret_cast<int*>(vendor + 8) = data_[0][2];
	//fprintf(stdout, "vendor: %s\n", vendor); // GenuineIntel or AuthenticAMD or other
	//fprintf(stdout, "vendor serialnumber: %08X%08X\n", data_[1][3], data_[1][0]);
	sprintf(str_buff, "%08X%08X", data_[1][3], data_[1][0]);
#else
	unsigned eax, ebx, ecx, edx;

	eax = 0; // processor info and feature bits
	native_cpuid(&eax, &ebx, &ecx, &edx);
	//fprintf(stdout, "%d, %d, %d, %d\n", eax, ebx, ecx, edx);

	*reinterpret_cast<int*>(vendor) = ebx;
	*reinterpret_cast<int*>(vendor + 4) = edx;
	*reinterpret_cast<int*>(vendor + 8) = ecx;
	//fprintf(stdout, "vendor: %s\n", vendor); // GenuineIntel or AuthenticAMD or other

	eax = 1; // processor serial number
	native_cpuid(&eax, &ebx, &ecx, &edx);

	// see the CPUID Wikipedia article on which models return the serial number in which registers
	//printf("vendor serialnumber: %08X%08X\n", edx, eax);
	sprintf(str_buff, "%08X%08X", edx, eax);
#endif
	str += str_buff;
	return 0;
}

三、AES加密

AES是一种对称加密方式,它的明文和密文长度是一致的,也就是它只能加密固定长度的东西,而且密码只有一个,不像RSA那样分公私。
如果想加密一个任意长度的数据,需要拆分,按块加密,最后剩下不足块长度的数据需要填充。但是填充完之后,其实你解密之后是不知道有没有填充的,假如原始数据正好就是你填充之后的样子呢?所以我这边记录了一下原始长度。
下面是加解密逻辑,块长度固定是16,然后滑窗加解密,加密最后一个块会补0,解密最后一个块会减掉补0的数据。完整的代码在看链接吧。

std::vector<uint8_t> aes_decrypt(std::vector<uint8_t> aes_data, uint8_t aes_key[32], int length)
{
	uint8_t* w; // expanded key
	w = aes_init(32);
	aes_key_expansion(aes_key, w);
	const int block = 16;
	char buff[block];
	uint8_t encrpty_buff[block];
	int encrypt_bytes = aes_data.size();
	int effect_bytes = length;
	int total_bytes = 0;
	std::vector<uint8_t> out_data;
	while (total_bytes < encrypt_bytes)
	{
		for (int i = 0; i < block; ++i)
		{
			buff[i] = aes_data[total_bytes + i];
		}

		aes_inv_cipher((uint8_t*)buff, encrpty_buff, w);
		if (total_bytes + block == encrypt_bytes)
		{
			for (int i = 0; i < effect_bytes - total_bytes; ++i)
			{
				out_data.push_back(encrpty_buff[i]);
			}
		}
		else
		{
			for (int i = 0; i < block; ++i)
			{
				out_data.push_back(encrpty_buff[i]);
			}
		}
		total_bytes += block;
	}
	free(w);
	return out_data;
}

std::vector<uint8_t> aes_encrypt(std::vector<uint8_t> data, uint8_t aes_key[32])
{
	uint8_t* w; // expanded key
	w = aes_init(32);
	aes_key_expansion(aes_key, w);
	const int block = 16;
	char buff[block];
	uint8_t encrpty_buff[block];
	int total_bytes = 0;
	std::vector<uint8_t> out_data;
	while (total_bytes < data.size())
	{
		int count = 0;
		for (int i = total_bytes; i < data.size() && count < block; ++i)
		{
			buff[count] = data[i];
			++count;
		}
		if (count != block)
		{
			for (int i = count; i < block; ++i)
			{
				buff[i] = 0;
			}
			aes_cipher((uint8_t*)buff, encrpty_buff, w);
		}
		else {
			aes_cipher((uint8_t*)buff, encrpty_buff, w);
		
		}
		for (int j = 0; j < block; ++j)
		{
			out_data.push_back(encrpty_buff[j]);
		}
		total_bytes += count;
	}
	free(w);
	return out_data;
}

四、MD5验签

RSA简介

首先我们要知道RSA加密是一种非对称加密,它的公钥和私钥是分开的。我们了解一下就可以知道,RSA的公钥是两个素数的乘积,它的私钥约等于需要知道这两个素数才可以得到,通过因数分解基本不可能得到私钥。所以RSA的安全性比较高。
RSA的明文和密文长度是不等的,明文长度只要比密钥长度短都可以加密,较长的明文需要分块加密。我是通过添加分隔符加解密的,因为RSA加密后的长度不是固定的。我这个方式没有遵循标准,加密后的东西用别的标准库估计打不开。

何谓md5验签?

md5验签就是:用RSA的私钥加密信息的MD5码,然后用公钥解密得到MD5与信息的MD5作校验对比。由于私钥是未知的,所以MD5码无法被篡改。

为什么用私钥加密?

MD5的公钥和私钥是可以对换加解密的,公钥加密的东西私钥可以解密,私钥加密的东西公钥也可以解密,并且公钥是公开的,所以用公钥解密,私钥加密。另外,你可以发现一般私钥的长度比公钥长很多,这个跟它生成时选择的一个固定数有关系。感兴趣的可以去看下原理。

完整代码链接

https://github.com/zhuqiang00099/license

### 如何生成 License 授权文件 在开源项目或商业软件开发过程中,生成合适的许可证文件对于保护知识产权和明确使用条款非常重要。以下是关于如何生成 License 文件的相关信息。 #### 开源项目的 License 文件生成工具 针对开源项目的需求,有多个工具可以帮助开发者快速生成适合的许可证文件: - **license-generator** 是一款命令行工具,支持为开源项目生成标准的 `LICENSE` 文件[^1]。该工具提供了简单易用的功能,可以通过指定参数来自定义许可证内容,例如年份、作者姓名及联系方式等信息[^2]。 使用此工具的方法如下: ```bash npm install -g license-generator lice mit --year 2023 --fullname "Your Name" ``` - 另外还有基于 Python 的工具 **LicenseGenerator**,它同样提供丰富的选项供用户选择不同的许可协议类型(MIT, GPL, Apache 等),并且可以根据具体需求调整模板[^4]。 #### 商业用途下的 License 授权管理方案 如果目标是创建用于控制软件使用的专有授权机制,则需要考虑更复杂的实现方式。这类场景通常涉及硬件绑定与时间限制等功能,下面列举了一些可能的技术方向及其特点: - 对于网络服务提供商来说,可以利用 IP 地址、MAC 地址作为唯一标识符来验证客户端身份;而对于桌面应用程序而言,可能会依赖 CPU 序列号或者主板序列号来进行校验[^5]。 - 华三通信(H3C) 提出了另一种思路——通过在其设备上加载特定类型的 Licenses 来激活某些高级特性。管理员可以在任何操作模式下调取当前系统的许可状态报告,并据此决定后续采购计划[^3]。 需要注意的是,构建这样的系统往往超出一般脚本编写范畴,涉及到加密算法设计、密钥分发策略等多个方面专业知识的应用。 ```python import hashlib def generate_license(serial_number, expiration_date): """Generate a simple hash-based license string.""" key = f"{serial_number}-{expiration_date}" hashed_key = hashlib.sha256(key.encode()).hexdigest() return hashed_key[:8].upper() # Return first 8 characters as the license code. # Example usage: print(generate_license("ABC123", "2024-12-31")) ``` 上述代码片段展示了一个非常基础的例子,实际应用中的安全措施会更加严格和完善。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值