生成唯一的机器 ID

我需要编写一个函数,该函数生成的 id 对于运行 Windows 操作系统的给定机器是唯一的。

目前,我正在使用 WMI 查询各种硬件参数,并将它们连接在一起,然后对它们进行散列以获得惟一的 id。我的问题是,我应该使用哪些建议参数? 目前,我使用 Bios CPU 磁盘数据的组合来生成唯一的 id。如果每个度量有多个结果,则使用第一个结果。

然而,我遇到了一个问题,一台双重引导到2个不同的 Windows 操作系统的机器在每个操作系统上生成不同的站点代码,这在理想情况下不应该发生。

作为参考,下面是我目前使用的度量标准:

Win32_Processor:UniqueID,ProcessorID,Name,Manufacturer,MaxClockSpeed
Win32_BIOS:Manufacturer
Win32_BIOS:SMBIOSBIOSVersion,IdentificationCode,SerialNumber,ReleaseDate,Version
Win32_DiskDrive:Model, Manufacturer, Signature, TotalHeads
Win32_BaseBoard:Model, Manufacturer, Name, SerialNumber
Win32_VideoController:DriverVersion, Name
112818 次浏览

You should look into using the MAC address on the network card (if it exists). Those are usually unique but can be fabricated. I've used software that generates its license file based on your network adapter MAC address, so it's considered a fairly reliable way to distinguish between computers.

What about just using the UniqueID of the processor?

Look up CPUID for one option. There might be some issues with multi-CPU systems.

Maybe cheating a little, but the MAC Address of a machines Ethernet adapter rarely changes without the motherboard changing these days.

I hate to be the guy who says, "you're just doing it wrong" (I always hate that guy ;) but...

Does it have to be repeatably generated for the unique machine? Could you just assign the identifier or do a public/private key? Maybe if you could generate and store the value, you could access it from both OS installs on the same disk?

You've probably explored these options and they doesn't work for you, but if not, it's something to consider.

If it's not a matter of user trust, you could just use MAC addresses.

Can you pull some kind of manufacturer serial number or service tag?

Our shop is a Dell shop, so we use the service tag which is unique to each machine to identify them. I know it can be queried from the BIOS, at least in Linux, but I don't know offhand how to do it in Windows.

Parse the SMBIOS yourself and hash it to an arbitrary length. See the PDF specification for all SMBIOS structures available.

To query the SMBIOS info from Windows you could use EnumSystemFirmwareEntries, EnumSystemFirmwareTables and GetSystemFirmwareTable.

IIRC, the "unique id" from the CPUID instruction is deprecated from P3 and newer.

In my program I first check for Terminal Server and use the WTSClientHardwareId. Else the MAC address of the local PC should be adequate.

If you really want to use the list of properties you provided leave out things like Name and DriverVersion, Clockspeed, etc. since it's possibly OS dependent. Try outputting the same info on both operating systems and leave out that which differs between.

I had the same problem and after a little research I decided the best would be to read MachineGuid in registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography, as @Agnus suggested. It is generated during OS installation and won't change unless you make another fresh OS install. Depending on the OS version it may contain the network adapter MAC address embedded (plus some other numbers, including random), or a pseudorandom number, the later for newer OS versions (after XP SP2, I believe, but not sure). If it's a pseudorandom theoretically it can be forged - if two machines have the same initial state, including real time clock. In practice, this will be rare, but be aware if you expect it to be a base for security that can be attacked by hardcore hackers.

Of course a registry entry can also be easily changed by anyone to forge a machine GUID, but what I found is that this would disrupt normal operation of so many components of Windows that in most cases no regular user would do it (again, watch out for hardcore hackers).

For one of my applications, I either use the computer name if it is non-domain computer, or the domain machine account SID for domain computers. Mark Russinovich talks about it in this blog post, Machine SID:

The final case where SID duplication would be an issue is if a distributed application used machine SIDs to uniquely identify computers. No Microsoft software does so and using the machine SID in that way doesn’t work just for the fact that all DC’s have the same machine SID. Software that relies on unique computer identities either uses computer names or computer Domain SIDs (the SID of the computer accounts in the Domain).

You can access the domain machine account SID via LDAP or System.DirectoryServices.

With our licensing tool we consider the following components

  • MAC Address
  • CPU (Not the serial number, but the actual CPU profile like stepping and model)
  • System Drive Serial Number (Not Volume Label)
  • Memory
  • CD-ROM model & vendor
  • Video Card model & vendor
  • IDE Controller
  • SCSI Controller

However, rather than just hashing the components and creating a pass/fail system, we create a comparable fingerprint that can be used to determine how different two machine profiles are. If the difference rating is above a specified tolerance then ask the user to activate again.

We've found over the last 8 years in use with hundreds of thousands of end-user installs that this combination works well to provide a reliably unique machine id - even for virtual machines and cloned OS installs.

Try this one, it gives a unique hard disk ID: Port of DiskId32 for Delphi 7-2010.

There is a library available for getting hardware specific informations: Hardware serial number extractor (CPU, RAM, HDD, BIOS)

I had an additional constraint, I was using .net express so I couldn't use the standard hardware query mechanism. So I decided to use power shell to do the query. The full code looks like this:

Private Function GetUUID() As String
Dim GetDiskUUID As String = "get-wmiobject Win32_ComputerSystemProduct  | Select-Object -ExpandProperty UUID"
Dim X As String = ""
Dim oProcess As New Process()
Dim oStartInfo As New ProcessStartInfo("powershell.exe", GetDiskUUID)
oStartInfo.UseShellExecute = False
oStartInfo.RedirectStandardInput = True
oStartInfo.RedirectStandardOutput = True
oStartInfo.CreateNoWindow = True
oProcess.StartInfo = oStartInfo
oProcess.Start()
oProcess.WaitForExit()
X = oProcess.StandardOutput.ReadToEnd
Return X.Trim()
End Function