获取唯一的机器ID

59

我想要获取唯一不可更改的机器ID,例如计算机的处理器序列号,以便分发软件并避免复制。

我曾尝试使用处理器序列号和硬盘序列号,但这些都会在格式化和重新安装Windows后发生变化。

您有什么想法如何获取计算机的不可更改的序列号吗?


解决什么问题?唯一的机器ID?处理器序列号?一个不可更改的序列号?要“无复制地”分发软件吗?每个问题的答案都不同,您想要哪个? - Dour High Arch
为什么对获取机器ID如此害怕?每个人都害怕回答这个问题...显然,这是为了防止复制。 - Brethlosze
13个回答

44

也许最简单的方法是, 获取DeviceId Nuget 包。

然后像这样使用:

string deviceId = new DeviceIdBuilder()
.AddMachineName()
.AddMacAddress()
.AddProcessorId()
.AddMotherboardSerialNumber()
.ToString();
你可以个性化使用以生成ID的信息。

Github 项目


你知道这个能在任何设备上运行吗?iPhone、Android等。 - Nate Pet
它不再工作了。 - Binozo
.AddProcessorId() .AddMotherboardSerialNumber() 这些在最新版本中不再可用。 - Wilhelm Sorban

19
你可以使用 WMI代码生成器。我猜你可以组合“键”(处理器ID、MAC和软件生成的密钥)。
using System.Management;
using System.Windows.Forms;

try
{
     ManagementObjectSearcher searcher = 
         new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor"); 

     foreach (ManagementObject queryObj in searcher.Get())
     {
         Console.WriteLine("-----------------------------------");
         Console.WriteLine("Win32_Processor instance");
         Console.WriteLine("-----------------------------------");
         Console.WriteLine("Architecture: {0}", queryObj["Architecture"]);
         Console.WriteLine("Caption: {0}", queryObj["Caption"]);
         Console.WriteLine("Family: {0}", queryObj["Family"]);
         Console.WriteLine("ProcessorId: {0}", queryObj["ProcessorId"]);
     }
}
catch (ManagementException e)
{
    MessageBox.Show("An error occurred while querying for WMI data: " + e.Message);
}

Win32_Processor

使用WMI在C#中检索硬件标识符, 作者Peter Bromberg


WMI不是唯一的。它没有任何独特的字段。我有一堆计算机,所有的WMI BIOS、CPU、显卡、网络名称都具有相同的值。 - Franck

17
如果你需要一个独特的ID,你必须先决定你对“独特”的定义。如果你想要用它来实现拷贝保护机制,那么使用一些简单的东西。这是因为如果有人真的想使用你的软件,他们将会找到打破保护的方法,只要时间和技能足够。在涉及到独特硬件ID的情况下,只需要考虑虚拟机就可以发现,任何东西都可以欺骗,所以有人可能会篡改你的软件。
在整个生命周期中,从PC中获取不了多少信息并将其视为唯一性标识。 (硬件更改很可能需要在某个时候重新生成ID。)如果你需要像这样的东西,应该考虑使用可以发送给客户的认证USB加密狗。
如果你只需要一些不那么难以获得的唯一标识符,你可以采用MAC地址(不可靠),操作系统序列号或域名和用户名,但所有这些都容易被伪造。但是,如果你的主要目标是锁定未授权的人员,则无法销售任何东西,因为如果安装、注册或从一个计算机移动到另一个计算机变得困难,没有人会想使用你的软件,尽管最后一个考虑因素是每台机器许可证的一部分。(这可能经常发生。)
作为第一步,使其容易:在你的目标用户群中使用一些简单的东西,这些东西不容易被欺骗。(例如,域名和用户名可以被企业客户更难地欺骗,因为他们的PC运行在实施策略等更大的环境中。)直到你有了这个之后,就忘掉其他的。
也许你可以把他们挡在门外,但这并不意味着他们会购买你的软件;他们只是不再使用它。你必须考虑的是有多少潜在客户由于你使程序变得如此复杂而不愿意或无法付款。

37
这个问题比仅仅复制保护软件更有用。因此,你不应该因为他只是在询问而进行恶意攻击。我也面临着同样的问题,试图获得每台计算机的唯一标识符,而我甚至没有打算销售我的软件。针对可以在世界各地分发的软件使用USB加密狗?做梦吧。 - Adrian Salazar
1
此外,还需要对企业级服务器端安装进行抄袭保护。这些类型的安装几乎不会经常更改,因此复杂的激活并不一定是坏事,因为它只需要完成一次,通常由供应商的现场安装团队完成。 - 7wp
3
我来这里是出于安全原因,而非许可证 - 我需要使用一些机器特定的密钥加密用户数据,以便在被窃取后(例如通过网络钓鱼)无法被检索。 - Tomáš Zato
@TomášZato:在这种情况下,您应该提出一个新问题,说明您的具体需求。但是如果您想快速解决问题,可以考虑使用盐值哈希或异步加密。 - Oliver
@TomášZato 实际上,我认为这是一个根本不同的问题。不同的ID用途有不同的要求。这不是一个一刀切的问题。 - Basic
显示剩余4条评论

17

我建议远离使用MAC地址。在某些硬件上,重新启动后MAC地址可能会更改。在我们的研究早期就已经明确不要依赖它。

请查看文章《软件保护和许可证开发》,其中提供了一些指导,说明如何设计和实现应用程序以减少盗版。

强制性免责声明和推广:我共同创立的公司生产OffByZero Cobalt许可解决方案。因此,你可能不会感到意外,我建议外包你的许可证,并专注于你的核心竞争力。


4
我同意。MAC地址非常不可靠。MAC地址列表可能会根据计算机连接到互联网的方式而发生变化。此外,每次启动计算机时,主适配器可能会改变。类似CISCO VPN等服务进一步增加了问题的复杂性。 - Santosh Tiwari

5
请查看这篇文章。它非常详尽,您将了解如何提取各种硬件信息。
引用自文章
要获取硬件信息,您需要创建ManagementObjectSearcher类的对象。
using System.Management;
ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from " + Key);
foreach (ManagementObject share in searcher.Get()) {
    // Some Codes ...
}

上述代码中的Key是一个变量,将被适当的数据替换。例如,要获取CPU信息,您需要用Win32_Processor替换Key。


1
我已经从文章中添加了一句引用。 - Laurent Etiemble

4

是的,我们可以获得一个由物理地址、独特驱动器ID、硬盘ID(卷序列号)、CPU ID和BIOS ID组成的代码。

例子(完整示例):
//Main physical hard drive ID
    private static string diskId()
    {
        return identifier("Win32_DiskDrive", "Model")
        + identifier("Win32_DiskDrive", "Manufacturer")
        + identifier("Win32_DiskDrive", "Signature")
        + identifier("Win32_DiskDrive", "TotalHeads");
    }
    //Motherboard ID
    private static string baseId()
    {
        return identifier("Win32_BaseBoard", "Model")
        + identifier("Win32_BaseBoard", "Manufacturer")
        + identifier("Win32_BaseBoard", "Name")
        + identifier("Win32_BaseBoard", "SerialNumber");
    }

如果我们更换硬盘,我猜ID会改变。 - Gray Programmerz

3

您不应该使用 MAC 地址,因为一些操作系统每天都会更改它。我的建议是:使用 Tools.CpuID.ProcessorId() + volumeSerial。

string volumeSerial = "";
    try {
        ManagementObject dsk = new ManagementObject(@"win32_logicaldisk.deviceid=""C:""");
        dsk.Get();
        volumeSerial = dsk["VolumeSerialNumber"].ToString();
    } catch {
        try {
            ManagementObject dsk = new ManagementObject(@"win32_logicaldisk.deviceid=""D:""");
            dsk.Get();
            volumeSerial = dsk["VolumeSerialNumber"].ToString();
        } catch { File.WriteAllText("disk.mising","need C or D"); Environment.Exit(0); }
    }

public class CpuID
    {
        [DllImport("user32", EntryPoint = "CallWindowProcW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
        private static extern IntPtr CallWindowProcW([In] byte[] bytes, IntPtr hWnd, int msg, [In, Out] byte[] wParam, IntPtr lParam);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool VirtualProtect([In] byte[] bytes, IntPtr size, int newProtect, out int oldProtect);

        const int PAGE_EXECUTE_READWRITE = 0x40;



        public static string ProcessorId()
        {
            byte[] sn = new byte[8];

            if (!ExecuteCode(ref sn))
                return "ND";

            return string.Format("{0}{1}", BitConverter.ToUInt32(sn, 4).ToString("X8"), BitConverter.ToUInt32(sn, 0).ToString("X8"));
        }

        private static bool ExecuteCode(ref byte[] result)
    {
        int num;

        /* The opcodes below implement a C function with the signature:
         * __stdcall CpuIdWindowProc(hWnd, Msg, wParam, lParam);
         * with wParam interpreted as an 8 byte unsigned character buffer.
         * */

        byte[] code_x86 = new byte[] {
            0x55,                      /* push ebp */
            0x89, 0xe5,                /* mov  ebp, esp */
            0x57,                      /* push edi */
            0x8b, 0x7d, 0x10,          /* mov  edi, [ebp+0x10] */
            0x6a, 0x01,                /* push 0x1 */
            0x58,                      /* pop  eax */
            0x53,                      /* push ebx */
            0x0f, 0xa2,                /* cpuid    */
            0x89, 0x07,                /* mov  [edi], eax */
            0x89, 0x57, 0x04,          /* mov  [edi+0x4], edx */
            0x5b,                      /* pop  ebx */
            0x5f,                      /* pop  edi */
            0x89, 0xec,                /* mov  esp, ebp */
            0x5d,                      /* pop  ebp */
            0xc2, 0x10, 0x00,          /* ret  0x10 */
        };
        byte[] code_x64 = new byte[] {
            0x53,                                     /* push rbx */
            0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, /* mov rax, 0x1 */
            0x0f, 0xa2,                               /* cpuid */
            0x41, 0x89, 0x00,                         /* mov [r8], eax */
            0x41, 0x89, 0x50, 0x04,                   /* mov [r8+0x4], edx */
            0x5b,                                     /* pop rbx */
            0xc3,                                     /* ret */
        };

        byte[] code;

        if (IsX64Process())
            code = code_x64;
        else 
            code = code_x86;

        IntPtr ptr = new IntPtr(code.Length);

        if (!VirtualProtect(code, ptr, PAGE_EXECUTE_READWRITE, out num))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

        ptr = new IntPtr(result.Length);

        try {
            return (CallWindowProcW(code, IntPtr.Zero, 0, result, ptr) != IntPtr.Zero);
        } catch { System.Windows.Forms.MessageBox.Show("Память повреждена"); return false; }
    }

        private static bool IsX64Process()
        {
            return IntPtr.Size == 8;
        }

    }

3
最近我遇到了一个情况,我使用了处理器ID和硬盘卷序列号,但两台笔记本电脑的ID完全相同...... - Fid
ProcessorId似乎不再生成唯一值:这里的所有机器都具有相同的ProcessorId(不同的Win版本,不同的供应商)...在Net 4.5上。 - jHilscher
一个真实生活中的结果数组示例: BFEBFBFF000006FBQ0WNWNFQF678084A BFEBFBFF000306A9NCYRXNJZF6815BA5 BFEBFBFF00030673K1HBRQ3ZCAF70541 078BFBFF000306A9BBW0BNBX1C70EEFF BFEBFBFF0001067AE0WRWOJZ68E3340B BFEBFBFF000306A9RCHBRRNTECACAE50 BFEBFBFF000306A9NBGVQNCCCC3A320F BFEBFBFF000206A7NBXGBRGDC642137D BFEBFBFF000306A9K0INZMKB12D2C5C7 BFEBFBFF00040651PAW0BOZV22B7BECF BFEBFBFF000306A9BCGRQMBRE829E19B 1F8BFBFF000306A9M1IWCMNYE2A678BC - Nik Shev
VolumeSerialNumber是在命令提示符下键入DIR时看到的8位十六进制值,例如“725A-02CD”。它不是内置于卷硬件中的 - 它是在格式化时创建的。在MS-DOS时代,这通常是唯一的(或者差不多)。 现在,PC制造商使用克隆软件在其机器上安装Windows,并且它们最终都具有相同的VolumeSerialNumber,因为它是从已被克隆的原始磁盘复制的。 - Phil Rogers

3

编辑:我刚刚看到你是指用c#。这里有一种更好的使用非托管代码的方法:

ManagementClass oMClass = new ManagementClass ("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection colMObj = oMCLass.GetInstances();
foreach(ManagementObject objMO in colMObj)
    Console.WriteLine(objMO["MacAddress"].ToString());

2
你知道MAC地址也可以被“欺骗”吗?另外,你的代码看起来像是C++,而不是C#? - Webleeuw
有什么解决方案吗?请给我建议。 - ush
4
如果处理器/硬盘序列号不足够的话,这就是你剩下的全部。如果他描述了他想要做什么而不是他想如何去做,我可能会有更好的回答。 - Blindy

3
我赞同Blindy的建议,使用(第一个?)网络适配器的MAC地址。是的,MAC地址可以被欺骗,但这会产生副作用(您不希望在同一网络中有两台具有相同MAC地址的PC),而且“普通海盗”为了使用您的软件不会去做这件事。考虑到没有100%的解决方案来防止软件盗版,我认为MAC地址是一个不错的折衷方案。
但请注意,当用户添加、更换或删除网络卡(或完全更换旧PC)时,该地址将发生变化,因此请准备好帮助客户并在他们更改硬件配置时提供新密钥。

6
我在做的项目中一直使用MAC地址作为设备的唯一标识符,但使用第一个地址时遇到了问题,因为它是MS Loopback Adapter,其MAC地址始终相同!我想分享这一点,以帮助其他人避免混淆! - Matt Winward
isLoopback() 对于该接口返回 true,但可以轻松跳过。需要注意的是,isVirtual() 不可靠,在 VirtualBox 中返回 false,目前在这里也是如此。 - enigment

0

我知道有两种可能的方法:

  1. 获取系统的处理器ID:

    public string getCPUId()
    {
        string cpuInfo = string.Empty;
        ManagementClass mc = new ManagementClass("win32_processor");
        ManagementObjectCollection moc = mc.GetInstances();
    
        foreach (ManagementObject mo in moc)
        {
            if (cpuInfo == "")
            {
                //仅获取第一个CPU的ID
                cpuInfo = mo.Properties["processorID"].Value.ToString();
                break;
            }
        }
        return cpuInfo;
    }
    
  2. 获取系统的UUID:

    public string getUUID()
    {
            Process process = new Process();
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
            startInfo.FileName = "CMD.exe";
            startInfo.Arguments = "/C wmic csproduct get UUID";
            process.StartInfo = startInfo;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.Start();
            process.WaitForExit();
            string output = process.StandardOutput.ReadToEnd();
            return output;
    }
    

以那种方式获取UUID是不好的。 - Gray Programmerz

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