首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 取得硬體識別碼完整實現:CPU ID、硬碟序號、MAC 位址跨平台方案

     編輯:關於C語言

前言

軟體授權、設備指紋、資產管理常需唯一硬體識別碼。C# 透過 System.Management (WMI) 及 P/Invoke NetAPI32 可取得 CPU ID、硬碟序號、MAC 位址。本文完整呈現 HardwareInfo 類別,並討論跨平台、權限、效能等實務考量。

HardwareInfo 完整實現

using System;
using System.Management;
using System.Runtime.InteropServices;

namespace Hardware
{
    public class HardwareInfo
    {
        // 取得機器名稱
        public string GetHostName() => System.Net.Dns.GetHostName();

        // 取得 CPU ID (WMI Win32_Processor)
        public string GetCpuID()
        {
            try
            {
                using (var mc = new ManagementClass("Win32_Processor"))
                using (var moc = mc.GetInstances())
                {
                    foreach (ManagementObject mo in moc)
                        return mo.Properties["ProcessorId"].Value?.ToString() ?? "";
                }
            }
            catch { }
            return "";
        }

        // 取得第一顆實體硬碟序號 (WMI Win32_PhysicalMedia)
        public string GetHardDiskID()
        {
            try
            {
                using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PhysicalMedia"))
                {
                    foreach (ManagementObject mo in searcher.Get())
                        return mo["SerialNumber"]?.ToString().Trim() ?? "";
                }
            }
            catch { }
            return "";
        }

        // 取得 MAC 位址 (P/Invoke NetAPI32 Netbios)
        public string GetMacAddress()
        {
            string addr = "";
            try
            {
                var lenum = new LANA_ENUM();
                var ncb = new NCB { ncb_command = (byte)NCBCONST.NCBENUM };
                int cb = Marshal.SizeOf(typeof(LANA_ENUM));
                ncb.ncb_buffer = Marshal.AllocHGlobal(cb);
                ncb.ncb_length = (ushort)cb;

                char uRetCode = Win32API.Netbios(ref ncb);
                lenum = (LANA_ENUM)Marshal.PtrToStructure(ncb.ncb_buffer, typeof(LANA_ENUM));
                Marshal.FreeHGlobal(ncb.ncb_buffer);

                if (uRetCode != (short)NCBCONST.NRC_GOODRET) return "";

                for (int i = 0; i < lenum.length; i++)
                {
                    ncb = new NCB { ncb_command = (byte)NCBCONST.NCBRESET, ncb_lana_num = lenum.lana[i] };
                    uRetCode = Win32API.Netbios(ref ncb);
                    if (uRetCode != (short)NCBCONST.NRC_GOODRET) continue;

                    ncb = new NCB
                    {
                        ncb_command = (byte)NCBCONST.NCBASTAT,
                        ncb_lana_num = lenum.lana[i],
                        ncb_callname = new byte[16] { (byte)'*' }
                    };
                    cb = Marshal.SizeOf(typeof(ADAPTER_STATUS)) + Marshal.SizeOf(typeof(NAME_BUFFER)) * 30;
                    ncb.ncb_buffer = Marshal.AllocHGlobal(cb);
                    ncb.ncb_length = (ushort)cb;

                    uRetCode = Win32API.Netbios(ref ncb);
                    var adapter = (ADAPTER_STATUS)Marshal.PtrToStructure(ncb.ncb_buffer, typeof(ADAPTER_STATUS));
                    Marshal.FreeHGlobal(ncb.ncb_buffer);

                    if (uRetCode == (short)NCBCONST.NRC_GOODRET)
                    {
                        if (i > 0) addr += ":";
                        addr += string.Format("{0:X2}{1:X2}{2:X2}{3:X2}{4:X2}{5:X2}",
                            adapter.adapter_address[0], adapter.adapter_address[1], adapter.adapter_address[2],
                            adapter.adapter_address[3], adapter.adapter_address[4], adapter.adapter_address[5]);
                    }
                }
            }
            catch { }
            return addr.Replace(' ', '0');
        }

        // --- P/Invoke 結構體與常數定義 (略,見原始碼) ---
        public enum NCBCONST { NCBNAMSZ = 16, MAX_LANA = 254, NCBENUM = 0x37, NRC_GOODRET = 0x00, NCBRESET = 0x32, NCBASTAT = 0x33, NUM_NAMEBUF = 30 }
        [StructLayout(LayoutKind.Sequential)] public struct ADAPTER_STATUS { public byte[] adapter_address; /* ...其他欄位...*/ }
        [StructLayout(LayoutKind.Sequential)] public struct NAME_BUFFER { public byte[] name; public byte name_num; public byte name_flags; }
        [StructLayout(LayoutKind.Sequential)] public struct NCB { public byte ncb_command; public byte ncb_retcode; public byte ncb_lsn; public byte ncb_num; public IntPtr ncb_buffer; public ushort ncb_length; public byte[] ncb_callname; public byte[] ncb_name; public byte ncb_rto; public byte ncb_sto; public IntPtr ncb_post; public byte ncb_lana_num; public byte ncb_cmd_cplt; public byte[] ncb_reserve; public IntPtr ncb_event; }
        [StructLayout(LayoutKind.Sequential)] public struct LANA_ENUM { public byte length; public byte[] lana; }
        [StructLayout(LayoutKind.Auto)] public struct ASTAT { public ADAPTER_STATUS adapt; public NAME_BUFFER[] NameBuff; }
        public class Win32API { [DllImport("NETAPI32.DLL")] public static extern char Netbios(ref NCB ncb); }
    }
}

三大識別碼取得方式對比

識別碼API優點缺點跨平台
CPU IDWMI Win32_Processor唯一、難偽造需 WMI 服務、虛擬機器可能相同僅 Windows
硬碟序號WMI Win32_PhysicalMedia實體唯一RAID/虛擬磁碟可能無序號、需管理員權限僅 Windows
MAC 位址NetAPI32 Netbios網路介面唯一多網卡、虛擬網卡、可偽造、P/Invoke 複雜Linux 需解析 /sys/class/net/*/address

跨平台現代方案 (.NET 6+)

// 取得所有網路介面 MAC (跨平台)
var macs = NetworkInterface.GetAllNetworkInterfaces()
    .Where(n => n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
    .Select(n => n.GetPhysicalAddress().ToString())
    .ToList();

// 取得機器唯一 ID (跨平台,需引用 Microsoft.Extensions.Hosting)
var machineId = Environment.MachineName; // 或讀取 /etc/machine-id (Linux)、HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid (Windows)

實務建議:組合指紋而非單一 ID

public string GetDeviceFingerprint()
{
    var parts = new List<string>();
    parts.Add(GetCpuID());
    parts.Add(GetHardDiskID());
    parts.Add(GetMacAddress());
    // 可加入主機板序號、BIOS UUID 等
    return string.Join("|", parts.Where(p => !string.IsNullOrEmpty(p)));
}

優點:單一硬體更換不導致指紋完全改變,容錯率高。

權限與效能注意事項

  • WMI 查詢:首次呼叫較慢 (數百毫秒),建議快取結果;需啟用 WMI 服務。
  • 硬碟序號:Win32_PhysicalMedia 需管理員權限,標準使用者會回傳空字串。
  • P/Invoke:Netbios 僅限 Windows,.NET Core / 5+ 需 DllImport("netapi32.dll", ExactSpelling=true, CharSet=CharSet.Ansi)。
  • 虛擬化環境:VMware/Hyper-V 虛擬機 CPU ID、MAC 可能重複,授權邏輯需考慮。

總結

C# 取得硬體識別碼主要依賴 WMI 與 P/Invoke,三大核心 ID (CPU、硬碟、MAC) 各有優缺。生產系統建議採用 組合指紋 + 快取 + 跨平台抽象層 架構,並在授權邏輯中容許硬體變更(如重新綁定流程)。現代 .NET 應優先使用 NetworkInterface 等跨平台 API,逐步移除 P/Invoke 依賴。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved