如何跨平台地获取几乎唯一的系统标识符?

31

我正在寻找一种方法来获取一个数字,该数字在不同机器上运行代码时几乎肯定会更改,并且在同一台机器上的两次运行之间几乎肯定保持不变。

如果我在Linux中将其作为shell脚本执行,我会使用类似下面这样的内容:

{ uname -n ; cat /proc/meminfo | head -n1 ; cat /proc/cpuinfo ; } | md5sum

但我需要在C++中实现这个功能(使用boost库),并且至少支持Windows、Linux和Mac操作系统。


6
在同一台计算机上,很可能无法获得一致的结果;大多数现代CPU都具有动态频率调节功能,而/proc/cpuinfo反映的是瞬时频率! - Oliver Charlesworth
可能是重复问题 - BoBTFish
2
这个程序看起来像是要进行网络化操作(否则ID就没有用处)。如果是这样的话,你最好从你的程序连接到的服务器获取一个唯一的ID并在本地存储以供后续使用。 - Nikos C.
@Nikos C.:实际上,我需要让服务器知道应用程序已被复制(包括所有文件),所以这种方法行不通...但这仍然是个好主意 :) - cube
1
使用本地盒子的MAC地址怎么样?您需要编写平台无关的代码来查找它,但只需要几行即可。 - Salgar
显示剩余2条评论
4个回答

60
为了生成一个大部分唯一的机器ID,您可以从系统上的各种硬件组件中获取几个序列号。大多数处理器都有CPU序列号,每个硬盘都有一个编号,每个网络卡都有一个唯一的MAC地址。
您可以获取这些信息并构建出机器的指纹。在将其视为新机器之前,您可能希望允许其中一些数字发生变化(例如,如果其中三个中有两个相同,则认为是同一台机器)。因此,您可以优雅地处理一些组件的升级。
我从我的一个项目中剪辑了一些代码来获取这些数字。
Windows:
#include "machine_id.h"   

#define WIN32_LEAN_AND_MEAN        
#include <windows.h>      
#include <intrin.h>       
#include <iphlpapi.h>     
#ifndef _MSC_VER
#include <cpuid.h>
#else
#include <intrin.h>
#endif

// we just need this for purposes of unique machine id. So any one or two mac's is       
// fine. 
u16 hashMacAddress( PIP_ADAPTER_INFO info )          
{        
   u16 hash = 0;          
   for ( u32 i = 0; i < info->AddressLength; i++ )   
   {     
      hash += ( info->Address[i] << (( i & 1 ) * 8 ));        
   }     
   return hash;           
}        

void getMacHash( u16& mac1, u16& mac2 )              
{        
   IP_ADAPTER_INFO AdapterInfo[32];                  
   DWORD dwBufLen = sizeof( AdapterInfo );           

   DWORD dwStatus = GetAdaptersInfo( AdapterInfo, &dwBufLen );                  
   if ( dwStatus != ERROR_SUCCESS )                  
      return; // no adapters.      

   PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;      
   mac1 = hashMacAddress( pAdapterInfo );            
   if ( pAdapterInfo->Next )       
      mac2 = hashMacAddress( pAdapterInfo->Next );   

   // sort the mac addresses. We don't want to invalidate     
   // both macs if they just change order.           
   if ( mac1 > mac2 )     
   {     
      u16 tmp = mac2;     
      mac2 = mac1;        
      mac1 = tmp;         
   }     
}        

u16 getVolumeHash()       
{        
   DWORD serialNum = 0;   

   // Determine if this volume uses an NTFS file system.      
   GetVolumeInformation( "c:\\", NULL, 0, &serialNum, NULL, NULL, NULL, 0 );    
   u16 hash = (u16)(( serialNum + ( serialNum >> 16 )) & 0xFFFF );              

   return hash;           
}        

u16 getCpuHash()          
{        
   int cpuinfo[4] = { 0, 0, 0, 0 };                  
   __cpuid( cpuinfo, 0 );          
   u16 hash = 0;          
   u16* ptr = (u16*)(&cpuinfo[0]); 
   for ( u32 i = 0; i < 8; i++ )   
      hash += ptr[i];     

   return hash;           
}        

const char* getMachineName()       
{        
   static char computerName[1024]; 
   DWORD size = 1024;     
   GetComputerName( computerName, &size );           
   return &(computerName[0]);      
}

Linux和OsX:

#include <stdio.h>
#include <string.h>
#include <unistd.h>          
#include <errno.h>           
#include <sys/types.h>       
#include <sys/socket.h>      
#include <sys/ioctl.h>  
#include <sys/resource.h>    
#include <sys/utsname.h>       
#include <netdb.h>           
#include <netinet/in.h>      
#include <netinet/in_systm.h>                 
#include <netinet/ip.h>      
#include <netinet/ip_icmp.h> 
#include <assert.h>

#ifdef DARWIN                    
#include <net/if_dl.h>       
#include <ifaddrs.h>         
#include <net/if_types.h>    
#else //!DARWIN              
// #include <linux/if.h>        
// #include <linux/sockios.h>   
#endif //!DARWIN               

const char* getMachineName() 
{ 
   static struct utsname u;  

   if ( uname( &u ) < 0 )    
   {       
      assert(0);             
      return "unknown";      
   }       

   return u.nodename;        
}   


//---------------------------------get MAC addresses ------------------------------------unsigned short-unsigned short----------        
// we just need this for purposes of unique machine id. So any one or two mac's is fine.            
unsigned short hashMacAddress( unsigned char* mac )                 
{ 
   unsigned short hash = 0;             

   for ( unsigned int i = 0; i < 6; i++ )              
   {       
      hash += ( mac[i] << (( i & 1 ) * 8 ));           
   }       
   return hash;              
} 

void getMacHash( unsigned short& mac1, unsigned short& mac2 )       
{ 
   mac1 = 0;                 
   mac2 = 0;                 

#ifdef DARWIN                

   struct ifaddrs* ifaphead; 
   if ( getifaddrs( &ifaphead ) != 0 )        
      return;                

   // iterate over the net interfaces         
   bool foundMac1 = false;   
   struct ifaddrs* ifap;     
   for ( ifap = ifaphead; ifap; ifap = ifap->ifa_next )                  
   {       
      struct sockaddr_dl* sdl = (struct sockaddr_dl*)ifap->ifa_addr;     
      if ( sdl && ( sdl->sdl_family == AF_LINK ) && ( sdl->sdl_type == IFT_ETHER ))                 
      {    
          if ( !foundMac1 )  
          {                  
             foundMac1 = true;                
             mac1 = hashMacAddress( (unsigned char*)(LLADDR(sdl))); //sdl->sdl_data) + sdl->sdl_nlen) );       
          } else {           
             mac2 = hashMacAddress( (unsigned char*)(LLADDR(sdl))); //sdl->sdl_data) + sdl->sdl_nlen) );       
             break;          
          }                  
      }    
   }       

   freeifaddrs( ifaphead );  

#else // !DARWIN             

   int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP );                  
   if ( sock < 0 ) return;   

   // enumerate all IP addresses of the system         
   struct ifconf conf;       
   char ifconfbuf[ 128 * sizeof(struct ifreq)  ];      
   memset( ifconfbuf, 0, sizeof( ifconfbuf ));         
   conf.ifc_buf = ifconfbuf; 
   conf.ifc_len = sizeof( ifconfbuf );        
   if ( ioctl( sock, SIOCGIFCONF, &conf ))    
   {       
      assert(0);             
      return;                
   }       

   // get MAC address        
   bool foundMac1 = false;   
   struct ifreq* ifr;        
   for ( ifr = conf.ifc_req; (char*)ifr < (char*)conf.ifc_req + conf.ifc_len; ifr++ ) 
   {       
      if ( ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data )          
         continue;  // duplicate, skip it     

      if ( ioctl( sock, SIOCGIFFLAGS, ifr ))           
         continue;  // failed to get flags, skip it    
      if ( ioctl( sock, SIOCGIFHWADDR, ifr ) == 0 )    
      {    
         if ( !foundMac1 )   
         { 
            foundMac1 = true;                 
            mac1 = hashMacAddress( (unsigned char*)&(ifr->ifr_addr.sa_data));       
         } else {            
            mac2 = hashMacAddress( (unsigned char*)&(ifr->ifr_addr.sa_data));       
            break;           
         } 
      }    
   }       

   close( sock );            

#endif // !DARWIN            

   // sort the mac addresses. We don't want to invalidate                
   // both macs if they just change order.    
   if ( mac1 > mac2 )        
   {       
      unsigned short tmp = mac2;        
      mac2 = mac1;           
      mac1 = tmp;            
   }       
} 

unsigned short getVolumeHash()          
{ 
   // we don't have a 'volume serial number' like on windows. Lets hash the system name instead.    
   unsigned char* sysname = (unsigned char*)getMachineName();       
   unsigned short hash = 0;             

   for ( unsigned int i = 0; sysname[i]; i++ )         
      hash += ( sysname[i] << (( i & 1 ) * 8 ));       

   return hash;              
} 

#ifdef DARWIN                
 #include <mach-o/arch.h>    
 unsigned short getCpuHash()            
 {         
     const NXArchInfo* info = NXGetLocalArchInfo();    
     unsigned short val = 0;            
     val += (unsigned short)info->cputype;               
     val += (unsigned short)info->cpusubtype;            
     return val;             
 }         

#else // !DARWIN             

 static void getCpuid( unsigned int* p, unsigned int ax )       
 {         
    __asm __volatile         
    (   "movl %%ebx, %%esi\n\t"               
        "cpuid\n\t"          
        "xchgl %%ebx, %%esi" 
        : "=a" (p[0]), "=S" (p[1]),           
          "=c" (p[2]), "=d" (p[3])            
        : "0" (ax)           
    );     
 }         

 unsigned short getCpuHash()            
 {         
    unsigned int cpuinfo[4] = { 0, 0, 0, 0 };          
    getCpuid( cpuinfo, 0 );  
    unsigned short hash = 0;            
    unsigned int* ptr = (&cpuinfo[0]);                 
    for ( unsigned int i = 0; i < 4; i++ )             
       hash += (ptr[i] & 0xFFFF) + ( ptr[i] >> 16 );   

    return hash;             
 }         
#endif // !DARWIN            

int main()
{

  printf("Machine: %s\n", getMachineName());
  printf("CPU: %d\n", getCpuHash());
  printf("Volume: %d\n", getVolumeHash());
  return 0;
}    

3
这段代码的一个好处是它考虑了用户拥有多个不同MAC地址的NIC的常见情况。 - Cody Gray
1
这比我想象的要复杂一些 :-) - cube
6
我可以很容易地通过终端更改MAC地址(ifconfig eth0 hw ether ...),CPUID并不是唯一的,而是由同一型号的所有处理器共享的,机器名称也可以很容易地更改。这种“独特性”很容易被伪造。 - Aleksei Petrenko

5
我知道这个问题有点过时,但我在许多场合都遇到了这个问题。我喜欢接受的解决方案,但如果您尝试了代码,就会知道它存在问题。
首先,CPU ID是产品ID,而不是序列号。因此,如果您在另一台服务器上拥有相同的CPU,则无法正常工作。此外,MAC地址可以轻松更改。
如果您只想在Linux上完成这项任务,可以尝试使用hal服务。例如:
hal-get-property --udi /org/freedesktop/Hal/devices/computer --key system.hardware.uuid

但是最好的方法可能是,如果您可以强制获得root访问权限,并且如果您想亲自动手-请查看dmidecode的代码。它将允许您提取机箱、BIOS、视频和系统的UUID。这是无法超越的 :) 并且通过少量调整,您可以将其转换为一个类。


0

也许您可以从唯一的硬件ID生成几乎唯一的ID - MAC是普遍唯一的,您还可以使用 CPU型号

我认为您应该只选择那些可能不经常更改的东西,例如CPU或LAN / WLAN卡。


1
您在回答中暗示了问题:局域网/无线局域网卡。许多机器都有多个网络卡,特别是同时拥有无线和有线卡的笔记本电脑。它们将各自有不同的MAC地址。您将检查哪一个?如果用户禁用其中一个并使用另一个呢? - Cody Gray
@claudio:他在Windows上不这样做。 - Rafael Baptista
@cody:获取它们所有并创建一个哈希表。 - Rafael Baptista
@rafael 是的,我刚注意到你在回答中发布的代码可以实现这个功能。这是个好主意,我喜欢它。你只需要确保调用任何枚举函数仍然会给你禁用的NICs。我不记得Windows上的GetAdaptersInfo是否会这样做。许多Windows函数完全忽略禁用的网络设备,这可能对移动用户造成问题。 - Cody Gray
@RafaelBaptista 没错,连接多个 ID 或同时使用多个 ID 应该“几乎”解决了这个问题。 - fadedreamz

0
一个相当便携的解决方案是使用当前可执行文件的修改时间。`stat`函数在Unix和Windows上都可用,尽管API不同,因此您需要使用一些`IFDEFs`。
二进制文件不太可能同时部署到不同的机器上,因此标识应该是唯一的。缺点是二进制更新将更改标识符。

如果只需要这些,它可以通过操作系统API本地生成GUID,或者通过服务器调用GUID生成器来生成。 - Rafael Baptista
@RafaelBaptista 但是UUID在系统重启后不会保留,而服务器组件会使解决方案变得复杂。但实际上,将随机UUID写入文件(如果缺失)是一个非常好的解决方案。非常便携,并且几乎不可能生成重复的ID。 - Jan Wrobel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接