軟體授權、裝置指紋、資產管理常需取得硬體唯一識別碼。本文整理 C# 透過 WMI (`System.Management`) 與 Windows API 取得 CPU ID、硬碟序號、MAC 位址、主機名稱的完整實作,含權限需求、效能優化、跨平台考量。
using System;
using System.Management;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
public static class HardwareId
{
// 取得主機名稱
public static string GetHostName() => Dns.GetHostName();
// 取得 CPU ID (處理器 ID)
public static string GetCpuId()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor");
foreach (ManagementObject obj in searcher.Get())
{
return obj["ProcessorId"]?.ToString()?.Trim() ?? string.Empty;
}
}
catch { /* 忽略 WMI 錯誤 */ }
return string.Empty;
}
// 取得第一顆實體硬碟序號
public static string GetDiskSerialNumber()
{
try
{
using var searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_PhysicalMedia");
foreach (ManagementObject obj in searcher.Get())
{
string serial = obj["SerialNumber"]?.ToString()?.Trim();
if (!string.IsNullOrEmpty(serial))
return serial;
}
}
catch { }
return string.Empty;
}
// 取得主要網卡 MAC 位址 (現代 .NET 推薦方式)
public static string GetMacAddress()
{
try
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up
&& nic.NetworkInterfaceType != NetworkInterfaceType.Loopback
&& !nic.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase)
&& !nic.Description.Contains("Hyper-V", StringComparison.OrdinalIgnoreCase))
.OrderByDescending(nic => nic.Speed); // 優先高速網卡
foreach (var nic in interfaces)
{
var mac = nic.GetPhysicalAddress();
if (mac != null && mac.Length == 6)
{
return string.Join(":", mac.GetAddressBytes().Select(b => b.ToString("X2")));
}
}
}
catch { }
return string.Empty;
}
// 綜合硬體指紋 (可自訂組合)
public static string GetHardwareFingerprint()
{
var parts = new[]
{
GetCpuId(),
GetDiskSerialNumber(),
GetMacAddress()
};
return string.Join("|", parts.Where(p => !string.IsNullOrEmpty(p)));
}
}
// 僅在 NetworkInformation 不可用時使用 (極舊系統)
public static class LegacyMacAddress
{
private const int MAX_ADAPTER_ADDRESS_LENGTH = 8;
[DllImport("iphlpapi.dll", CharSet = CharSet.Ansi)]
private static extern int GetAdaptersInfo(IntPtr pAdapterInfo, ref int pOutBufLen);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct IP_ADAPTER_INFO
{
public IntPtr Next;
public int ComboIndex;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string AdapterName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 132)]
public string Description;
public uint AddressLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_ADAPTER_ADDRESS_LENGTH)]
public byte[] Address;
public int Index;
public uint Type;
public uint DhcpEnabled;
public IntPtr CurrentIpAddress;
public IP_ADAPTER_INFO_LIST IpAddressList;
public IP_ADAPTER_INFO_LIST GatewayList;
public IP_ADAPTER_INFO_LIST DhcpServerList;
public bool HaveWins;
public IP_ADAPTER_INFO_LIST PrimaryWinsServer;
public IP_ADAPTER_INFO_LIST SecondaryWinsServer;
public int LeaseObtained;
public int LeaseExpires;
}
public static string GetMacAddress()
{
int bufferSize = 0;
GetAdaptersInfo(IntPtr.Zero, ref bufferSize);
if (bufferSize == 0) return string.Empty;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
try
{
int result = GetAdaptersInfo(buffer, ref bufferSize);
if (result != 0) return string.Empty;
IntPtr current = buffer;
while (current != IntPtr.Zero)
{
var adapter = Marshal.PtrToStructure(current);
if (adapter.AddressLength == 6)
{
return string.Join(":", adapter.Address.Take(6).Select(b => b.ToString("X2")));
}
current = adapter.Next;
}
}
finally
{
Marshal.FreeHGlobal(buffer);
}
return string.Empty;
}
}
// 快取範例
public static class HardwareIdCache
{
private static readonly Lazy _cpuId = new(HardwareId.GetCpuId, true);
private static readonly Lazy _diskSerial = new(HardwareId.GetDiskSerialNumber, true);
private static readonly Lazy _mac = new(HardwareId.GetMacAddress, true);
public static string CpuId => _cpuId.Value;
public static string DiskSerial => _diskSerial.Value;
public static string MacAddress => _mac.Value;
}
public class LicenseValidator
{
public bool Validate(string licenseKey, string expectedFingerprint)
{
// 1. 解析授權碼中的指紋 (或線上驗證)
// 2. 比對當前硬體指紋
string current = HardwareIdCache.GetHardwareFingerprint();
// 允許部分硬體變更 (如更換網卡)
return CalculateSimilarity(current, expectedFingerprint) > 0.8;
}
private double CalculateSimilarity(string current, string expected)
{
var currentParts = current.Split('|');
var expectedParts = expected.Split('|');
int match = currentParts.Intersect(expectedParts).Count();
return (double)match / Math.Max(currentParts.Length, expectedParts.Length);
}
}
硬體識別碼取得看似簡單,但涉及權限、虛擬化、跨平台、效能等實務考量。建立可靠的硬體指紋需組合多來源、容忍部分變更、妥善處理異常情境。