软件激活功能的实现方法

常用激活方式

完全离线激活

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

规则示例:

  • 激活码由四个两数组成 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 上获取硬盘序列号

1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <errno.h>
5
6#include <linux/limits.h>
7#include <libmount/libmount.h>
8#include <libudev.h>
9
10#define SERIAL_MAX_LENGTH 1024
11
12// 通过 libmount 获取挂载点的硬盘设备路径
13const char* device_path_of_mount_point(const char* mount_point)
14{
15    static char device_path[PATH_MAX];
16    device_path[0] = 0;
17
18    struct libmnt_context* ctx = mnt_new_context();
19    if (ctx == NULL)
20        return NULL;
21
22    struct libmnt_table* table = NULL;
23
24    if (mnt_context_get_fstab(ctx, &table) < 0)
25    {
26        mnt_free_context(ctx);
27        return NULL;
28    }
29
30    struct libmnt_fs* fs = mnt_table_find_target(table, mount_point, MNT_ITER_BACKWARD);
31    if (fs == NULL)
32    {
33        mnt_free_context(ctx);
34        return NULL;
35    }
36
37    const char* path = mnt_fs_get_srcpath(fs);
38    strncpy(device_path, path, PATH_MAX);
39    mnt_free_context(ctx);
40    return device_path;
41}
42
43
44// 通过 libudev 获取设备路径的序列号
45const char* serial_of_device(const char* device_path)
46{
47    static char device_serial[SERIAL_MAX_LENGTH];
48    device_serial[0] = 0;
49
50    struct udev* udev = udev_new();
51    if (udev == NULL)
52        return NULL;
53
54    struct udev_enumerate* enumerate = udev_enumerate_new(udev);
55    if (enumerate == NULL)
56    {
57        udev_unref(udev);
58        return NULL;
59    }
60
61    if (udev_enumerate_scan_devices(enumerate) < 0)
62    {
63        udev_enumerate_unref(enumerate);
64        udev_unref(udev);
65        return NULL;
66    }
67
68    struct udev_list_entry* device_entry = udev_enumerate_get_list_entry(enumerate);
69    udev_list_entry_foreach(device_entry, device_entry)
70    {
71        const char* syspath = udev_list_entry_get_name(device_entry);
72        struct udev_device* device = udev_device_new_from_syspath(udev, syspath);
73        struct udev_list_entry* link_entry = udev_device_get_devlinks_list_entry(device);
74
75        udev_list_entry_foreach(link_entry, link_entry)
76        {
77            const char* link = udev_list_entry_get_name(link_entry);
78            if (strcmp(link, device_path) == 0)
79            {
80                const char* serial = udev_device_get_property_value(device, "ID_SERIAL");
81                strncpy(device_serial, serial, SERIAL_MAX_LENGTH);
82                udev_device_unref(device);
83                goto SUCCESS;
84            }
85        }
86
87        udev_device_unref(device);
88    }
89
90SUCCESS:
91    udev_enumerate_unref(enumerate);
92    udev_unref(udev);
93    return device_serial;
94}
95
96int main (int argc, char *argv[]) 
97{
98    const char* device_path = device_path_of_mount_point("/");
99    if (device_path == NULL)
100        return EXIT_FAILURE;
101    
102    const char* device_serial = serial_of_device(device_path);
103    printf("Path: %s \nSerial: %s\n", device_path, device_serial);
104    return EXIT_SUCCESS;
105}

Windows 上获取硬盘序列号

1#include <Windows.h>
2#include <stdio.h>
3#include <stdlib.h>
4
5#define SERIAL_MAX_LENGTH 1024
6
7const char* serial_of_device(const char* device_path)
8{
9    static char device_serial[SERIAL_MAX_LENGTH];
10    device_serial[0] = 0;
11
12    HANDLE device = CreateFileA(device_path, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
13    if (device == INVALID_HANDLE_VALUE)
14        return NULL;
15
16    STORAGE_PROPERTY_QUERY query;
17    query.PropertyId = StorageDeviceProperty;
18    query.QueryType  = PropertyStandardQuery;
19
20    // 查询存储器头
21    STORAGE_DESCRIPTOR_HEADER header;
22    BOOL success = DeviceIoControl(device, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &header, sizeof(header), NULL, NULL);
23    if (!success || header.Size == 0)
24    {
25        CloseHandle(device);
26        return NULL;
27    }
28
29    // 查询存储器全部属性
30    void* buffer = malloc(header.Size);
31    if (buffer == NULL)
32    {
33        CloseHandle(device);
34        return NULL;
35    }
36    success = DeviceIoControl(device, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), buffer, header.Size, NULL, NULL);
37    if (!success)
38    {
39        free(buffer);
40        CloseHandle(device);
41        return NULL;
42    }
43
44    // 获取序列号的偏移并读取序列号
45    const STORAGE_DEVICE_DESCRIPTOR* descriptor = (STORAGE_DEVICE_DESCRIPTOR*)buffer;
46    strncpy(device_serial, (char*)buffer + descriptor->SerialNumberOffset, SERIAL_MAX_LENGTH);
47
48    free(buffer);
49    CloseHandle(device);
50    return device_serial;
51}
52
53int main()
54{
55    const char* device_serial = serial_of_device("\\\\.\\C:");
56    if (device_serial == NULL)
57        return EXIT_FAILURE;
58
59    printf("%s\n", device_serial);
60    return EXIT_SUCCESS;
61}