Windows系统获取设备唯一标识

什么是唯一标识

唯一的标识一个设备是一个基本功能,可以拥有很多应用场景,比如软件授权(如何保证你的软件在授权后才能在特定机器上使用)、软件License,设备标识,设备身份识别等。

有哪些唯一标识

  • 网卡MAC地址
  • CPUID
  • 硬盘序列号
  • 自定义算法
  • Windows产品ID
  • MachineGUID
  • 主板SmBIOS UUID

网卡MAC地址

优缺点

优点缺点
获取方便,兼容性高一个电脑可能存在多个网卡,也就是多个MAC地址。而且MAC地址很容易被改变。

获取方式

//获取所有网卡的信息
#include <WinSock2.h>
#include <Iphlpapi.h>
#include <iostream>
#include <assert.h>
#include <string>
#pragma comment(lib,"Iphlpapi.lib")
using namespace std;
int main(){
    unsigned long stSize = 0;
    int ret = GetAdaptersInfo(nullptr, &stSize);
    assert(ret == ERROR_BUFFER_OVERFLOW);
    IP_ADAPTER_INFO* adapterInfo = (IP_ADAPTER_INFO*)new uint8_t[stSize];
    ret = GetAdaptersInfo(adapterInfo, &stSize);
    assert(ret == ERROR_SUCCESS);
    int cardNum = 0;
    IP_ADAPTER_INFO* info = adapterInfo;
    while (info) {
        cardNum++;
        cout << "网卡:" << cardNum<<endl;
        cout << "网卡名称:" << info->AdapterName << endl;
        cout << "网卡描述:" << info->Description << endl;

        switch (info->Type)
        {
        case MIB_IF_TYPE_OTHER:cout << "网卡类型:" << "OTHER" << endl;break;
        case MIB_IF_TYPE_ETHERNET:cout << "网卡类型:" << "ETHERNET" << endl;break;
        case MIB_IF_TYPE_TOKENRING:cout << "网卡类型:" << "TOKENRING" << endl;break;
        case MIB_IF_TYPE_FDDI:cout << "网卡类型:" << "FDDI" << endl;break;
        case MIB_IF_TYPE_PPP:printf("PP\n");cout << "网卡类型:" << "PPP" << endl;break;
        case MIB_IF_TYPE_LOOPBACK:cout << "网卡类型:" << "LOOPBACK" << endl;break;
        case MIB_IF_TYPE_SLIP:cout << "网卡类型:" << "SLIP" << endl;break;
        default:break;
        }
        for (DWORD i = 0; i < info->AddressLength; i++) {
            if (i < info->AddressLength - 1)
                printf("%02X-", info->Address[i]);
            else
                printf("%02X\n", info->Address[i]);
        }
        cout << endl;
        info = info->Next;
    }
    delete[] adapterInfo;
    return 0;
}

CPUID

优缺点

优点缺点
Intel现在可能同一批次的CPU ID都一样,不再提供唯一的ID,不能用作唯一ID

获取方式

CPUID的获取方式比较复杂,所以这里提供一个简易方法。 在Windows系统中通过命令行运行

wmic cpu get processorid

就可以查看CPU ID。

或者使用以下代码,获取CPUID。注意仅限 cl编译器的x86模式,否则无法编译通过。

#include <stdio.h>
int main() {
    unsigned long s1 = 0;
    unsigned long s2 = 0;
    unsigned long s3 = 0;
    unsigned long s4 = 0;
    __asm
    {
        mov eax, 00h
        xor edx, edx
        cpuid
        mov s1, edx
        mov s2, eax
    }
    __asm
    {
        mov eax, 01h
        xor ecx, ecx
        xor edx, edx
        cpuid
        mov s3, edx
        mov s4, ecx
    }
    static char buf[100];
    sprintf_s(buf, "%08X%08X%08X%08X", s1, s2, s3, s4);
    printf(buf);
    return 0;
}

硬盘序列号

优缺点

优点缺点
有些机器可能存在多块硬盘,更换硬盘ID也随之变化,而且有的电脑可能没有硬盘。

获取方式

在Windows系统中通过命令行运行wmic diskdrive get serialnumber就可以获取硬盘ID。

<bv/>

自定义算法

​ 可以使用自制的一个特定算法例如随机数生成唯一的ID,然后写入到设备硬盘上,作为其唯一标识。

优缺点

优点缺点
不依赖硬件容易伪造,容易擦除。

生成方式

通常可以使用长随机字符串或者UUID等方式,此处列出一种UUID的生成。

//跨平台的UUID生成
#include <stdio.h>
#include <stdint.h>
#include <string>
#include <iostream>

#if defined(_WIN32)
#include <objbase.h>
#elif defined(__linux__)
#include <uuid/uuid.h>
#endif
#define GUID_LEN 64
using std::string;

#if defined(_WIN32)
string generate() {
    char buf[GUID_LEN] = { 0 };
    GUID guid;
    if (CoCreateGuid(&guid))
        return string("");

    sprintf_s(buf,
        "%08X-%04X-%04x-%02X%02X-%02X%02X%02X%02X%02X%02X",
        guid.Data1, guid.Data2, guid.Data3,
        guid.Data4[0], guid.Data4[1], guid.Data4[2],
        guid.Data4[3], guid.Data4[4], guid.Data4[5],
        guid.Data4[6], guid.Data4[7]);
    return std::string(buf);
}
#elif defined(__linux__)
string generate() {
    char buf[1000];
    uuid_t uid;
    uuid_generate(uid);
    uuid_unparse(uid, buf);
    return string(buf);
}
#endif
int main() {
    std::cout << generate();

    return 0;
}

Centos下需要运行命令yum install libuuid libuuid-devel并且使用 -luuid才能编译成功。

Ubuntu下则需要执行sudo apt-get install uuid-dev

Windows的产品ID

优缺点

优点缺点
获取方便并不唯一,重复概率较大,并不推荐。

获取方法

#include <iostream>
#include <assert.h>
#include "windows.h"
#include "tchar.h"
#include "stdio.h"
using namespace std;

int main(){
    HKEY hkey;
    LPCTSTR data_set = _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
    if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, data_set, 0, KEY_READ | KEY_WOW64_64KEY, &hkey)){
        char dwValue[256] = {0};
        DWORD dwSzType = REG_SZ;
        DWORD dwSize = sizeof(dwValue);
        LSTATUS ret = RegQueryValueExA(hkey, "ProductId", 0, &dwSzType, (LPBYTE)&dwValue, &dwSize);
        assert(ret == ERROR_SUCCESS);
        cout << dwValue << endl;
    }
    RegCloseKey(hkey);
    return 0;
}

MachineGUID

优缺点

优点缺点
获取方便,唯一性高。重装系统后会改变。

获取方法

#include <iostream>
#include <assert.h>
#include "windows.h"
#include "tchar.h"
#include "stdio.h"
using namespace std;

int main(){
    HKEY hkey;
    LPCTSTR data_set = _T("SOFTWARE\\Microsoft\\Cryptography");
    if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, data_set, 0, KEY_READ | KEY_WOW64_64KEY, &hkey)){
        char dwValue[256] = {0};
        DWORD dwSzType = REG_SZ;
        DWORD dwSize = sizeof(dwValue);
        LSTATUS ret = RegQueryValueExA(hkey, "MachineGuid", 0, &dwSzType, (LPBYTE)&dwValue, &dwSize);
        assert(ret == ERROR_SUCCESS);
        cout << dwValue << endl;
    }
    RegCloseKey(hkey);
    return 0;
}

SmBIOS UUID(推荐)

优缺点

优点缺点
唯一性高,重装系统后也不会改变不是所有主板厂商都提供UUID

如果厂商没有提供UUID,wmic会返回“FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF”,即一个无效的UUID。

获取方法

在Windows系统中通过命令行运行wmic csproduct get UUID就可以获取硬盘ID。

写在最后

有很多唯一ID的获取比较麻烦,推荐使用命令行获取,下面这个例子以获取SmBIOS UUID为例,使用代码调用命令行来获取相应数据,代码对于其他需要使用命令行获取的方法均有效。获取到命令行返回的数据后再对应不同的返回数据进行处理即可。

#include <iostream>
#include <assert.h>
#include "windows.h"
#include "stdio.h"
using namespace std;

int main(){
    const char* CommandLine = "wmic csproduct get UUID"; //修改这里
    SECURITY_ATTRIBUTES  sa;
    HANDLE hRead, hWrite;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;
    if (!CreatePipe(&hRead, &hWrite, &sa, 0)) {
        return -1;
    }
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    si.cb = sizeof(STARTUPINFO);
    GetStartupInfoA(&si);
    si.hStdError = hWrite;
    si.hStdOutput = hWrite;
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    if (!CreateProcessA(NULL, (char*)CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi)) {
        return -2;
    }
    CloseHandle(hWrite);
    std::string result;
    char buffer[4096] = { 0 };
    DWORD bytesRead;
    while (true) {
        memset(buffer, 0, strlen(buffer));
        if (ReadFile(hRead, buffer, 4095, &bytesRead, NULL) == NULL)
            break;
        result.append(buffer);
    }
    cout << result;
    return 0;
}