软件激活功能的实现方法

软件激活功能的实现方法

常用激活方式

完全离线激活

通过某种算法规则生成激活码,软件通过该规则对激活码进行验证。用户只要将激活码填入软件即可。

规则示例:

  • 激活码由四个两数组成 AA-BB-CC-DD
  • 规则为 DD = (AA + BB + CC) % 100

这种方法使用起来最方便,但安全性极差,一个激活码即可无限激活。

部分离线激活

设备本身离线,但激活时需要其他联网设备复制进行,激活后使用软件不需要联网。

步骤:

  • 软件获取设备 ID
  • 用户通过其他联网环境再注册网页上提交设备 ID
  • 注册服务器根据设备ID,以某种算法规则生成注册码返回给用户
  • 用户在软件上填入该激活码激活
  • 软件以该规则进行验证

规则示例:

  • 激活码 = SHA256(设备ID)

这种方法使用起来也较为方便,且一个激活码只能用于一台设备。但也有算法被破解的风险。

在线激活

设备全程联网验证。这种方式最安全,但对用户而言十分麻烦。

获取唯一设备 ID

X86 汇编中有一条 cpuid 指令,可以获取 CPU 的 ID,原本包含 CPU 的序列号,但出于隐私保护的原因被取消了,现在 CPU ID 仅含 CPU 型号,而不包含序列号。

可以采用 硬盘序列号 作为设备ID,获取方法如下:

Linux 上获取硬盘序列号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <linux/limits.h>
#include <libmount/libmount.h>
#include <libudev.h>

#define SERIAL_MAX_LENGTH 1024

// 通过 libmount 获取挂载点的硬盘设备路径
const char* device_path_of_mount_point(const char* mount_point)
{
    static char device_path[PATH_MAX];
    device_path[0] = 0;

    struct libmnt_context* ctx = mnt_new_context();
    if (ctx == NULL)
        return NULL;

    struct libmnt_table* table = NULL;

    if (mnt_context_get_fstab(ctx, &table) < 0)
    {
        mnt_free_context(ctx);
        return NULL;
    }

    struct libmnt_fs* fs = mnt_table_find_target(table, mount_point, MNT_ITER_BACKWARD);
    if (fs == NULL)
    {
        mnt_free_context(ctx);
        return NULL;
    }

    const char* path = mnt_fs_get_srcpath(fs);
    strncpy(device_path, path, PATH_MAX);
    mnt_free_context(ctx);
    return device_path;
}


// 通过 libudev 获取设备路径的序列号
const char* serial_of_device(const char* device_path)
{
    static char device_serial[SERIAL_MAX_LENGTH];
    device_serial[0] = 0;

    struct udev* udev = udev_new();
    if (udev == NULL)
        return NULL;

    struct udev_enumerate* enumerate = udev_enumerate_new(udev);
    if (enumerate == NULL)
    {
        udev_unref(udev);
        return NULL;
    }

    if (udev_enumerate_scan_devices(enumerate) < 0)
    {
        udev_enumerate_unref(enumerate);
        udev_unref(udev);
        return NULL;
    }

    struct udev_list_entry* device_entry = udev_enumerate_get_list_entry(enumerate);
    udev_list_entry_foreach(device_entry, device_entry)
    {
        const char* syspath = udev_list_entry_get_name(device_entry);
        struct udev_device* device = udev_device_new_from_syspath(udev, syspath);
        struct udev_list_entry* link_entry = udev_device_get_devlinks_list_entry(device);

        udev_list_entry_foreach(link_entry, link_entry)
        {
            const char* link = udev_list_entry_get_name(link_entry);
            if (strcmp(link, device_path) == 0)
            {
                const char* serial = udev_device_get_property_value(device, "ID_SERIAL");
                strncpy(device_serial, serial, SERIAL_MAX_LENGTH);
                udev_device_unref(device);
                goto SUCCESS;
            }
        }

        udev_device_unref(device);
    }

SUCCESS:
    udev_enumerate_unref(enumerate);
    udev_unref(udev);
    return device_serial;
}

int main (int argc, char *argv[]) 
{
    const char* device_path = device_path_of_mount_point("/");
    if (device_path == NULL)
        return EXIT_FAILURE;
    
    const char* device_serial = serial_of_device(device_path);
    printf("Path: %s \nSerial: %s\n", device_path, device_serial);
    return EXIT_SUCCESS;
}

Windows 上获取硬盘序列号

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>

#define SERIAL_MAX_LENGTH 1024

const char* serial_of_device(const char* device_path)
{
    static char device_serial[SERIAL_MAX_LENGTH];
    device_serial[0] = 0;

    HANDLE device = CreateFileA(device_path, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (device == INVALID_HANDLE_VALUE)
        return NULL;

    STORAGE_PROPERTY_QUERY query;
    query.PropertyId = StorageDeviceProperty;
    query.QueryType  = PropertyStandardQuery;

    // 查询存储器头
    STORAGE_DESCRIPTOR_HEADER header;
    BOOL success = DeviceIoControl(device, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &header, sizeof(header), NULL, NULL);
    if (!success || header.Size == 0)
    {
        CloseHandle(device);
        return NULL;
    }

    // 查询存储器全部属性
    void* buffer = malloc(header.Size);
    if (buffer == NULL)
    {
        CloseHandle(device);
        return NULL;
    }
    success = DeviceIoControl(device, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), buffer, header.Size, NULL, NULL);
    if (!success)
    {
        free(buffer);
        CloseHandle(device);
        return NULL;
    }

    // 获取序列号的偏移并读取序列号
    const STORAGE_DEVICE_DESCRIPTOR* descriptor = (STORAGE_DEVICE_DESCRIPTOR*)buffer;
    strncpy(device_serial, (char*)buffer + descriptor->SerialNumberOffset, SERIAL_MAX_LENGTH);

    free(buffer);
    CloseHandle(device);
    return device_serial;
}

int main()
{
    const char* device_serial = serial_of_device("\\\\.\\C:");
    if (device_serial == NULL)
        return EXIT_FAILURE;

    printf("%s\n", device_serial);
    return EXIT_SUCCESS;
}
作者: PlanC
2024-12-18 21:18:31+08:00