Windows系统获取设备唯一标识
什么是唯一标识
唯一的标识一个设备是一个基本功能,可以拥有很多应用场景,比如软件授权(如何保证你的软件在授权后才能在特定机器上使用)、软件License,设备标识,设备身份识别等。
有哪些唯一标识
-
网卡MAC地址
-
CPUID
-
硬盘序列号
-
自定义算法
-
Windows产品ID
-
MachineGUID
-
主板SmBIOS UUID
网卡MAC地址
优缺点
优点 | 缺点 |
---|---|
获取方便,兼容性高 | 一个电脑可能存在多个网卡,也就是多个MAC地址。而且MAC地址很容易被改变。 |
获取方式
C++
//获取所有网卡的信息
#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模式,否则无法编译通过。
C++
#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。
自定义算法
可以使用自制的一个特定算法例如随机数生成唯一的ID,然后写入到设备硬盘上,作为其唯一标识。
优缺点
优点 | 缺点 |
---|---|
不依赖硬件 | 容易伪造,容易擦除。 |
生成方式
通常可以使用长随机字符串或者UUID等方式,此处列出一种UUID的生成。
C++
//跨平台的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
优缺点
优点 | 缺点 |
---|---|
获取方便 | 并不唯一,重复概率较大,并不推荐。 |
获取方法
C++
#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
优缺点
优点 | 缺点 |
---|---|
获取方便,唯一性高。 | 重装系统后会改变。 |
获取方法
C++
#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
为例,使用代码调用命令行来获取相应数据,代码对于其他需要使用命令行获取的方法均有效。获取到命令行返回的数据后再对应不同的返回数据进行处理即可。
C++
#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;
}