前言
有时,我们为了保护自己写的程序不被滥用(搞点钱),会想绑定硬件,还要设置使用期限。本文就实践了一种license方案,当然没有考虑逆向,这个方面也不在讨论的范畴,所以大家要保护自己的代码时请充分调研!!!
一、总体方案设计
- 获取硬件码,这个可以是CPU序号、MAC地址等这些基本不会变的东西。
- 设定截止日期等信息,把你要校验的信息(包括硬件码)一起做一个加密,例如AES加密(有人可能会问都加密了,不是已经好了吗,后面再解释)。
- 把上面加密后的校验信息我们做一个MD5验签,就是把校验信息计算一个MD5摘要,然后用RSA加密,检验校验信息是否是伪造的。
- 我们用base64编码一下上面这些信息,让它保存成文本写入文件。
- 验证文件,首先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的公钥和私钥是可以对换加解密的,公钥加密的东西私钥可以解密,私钥加密的东西公钥也可以解密,并且公钥是公开的,所以用公钥解密,私钥加密。另外,你可以发现一般私钥的长度比公钥长很多,这个跟它生成时选择的一个固定数有关系。感兴趣的可以去看下原理。
545

被折叠的 条评论
为什么被折叠?



