How to write hardware id generator?

Developers, who write in C#, VB.Net, or any other .Net language, often ask how to get and check hardware id in their favorite language. No matter what language you use, the idea is the same. In this tutorial, we will show how to calculate the hardware id in C#. The sample we will create in the tutorial can be easily adapted for any .Net language.

You can find complete source code on GitHub:
https://github.com/Softanics/get-hardware-id-csharp

What is hardware id?

Hardware id is a fingerprint of a machine, that differ from one machine to another. In other words, the hardware id is a unique machine identifier. They are used to make software working on a particular machine only. An application compares hardware id stored in a user serial key with a machine hardware id. If they are equal, the application continues working in full mode. Otherwise, it shows an error message, saying the application is unable to run on this machine, and then exits. The process of issuing a serial key with a hardware id is called software activation.

How to get a machine fingerprint?

An algorithm of hardware id generation must output the same number each time and output different numbers on different machines. The solution should combine characteristics of hardware, and mix them.

On Windows, you can get information about different hardware: a CPU, a motherboard, an HDD. Notice that if hardware id uses information about a CPU, it will need a new license key, if the CPU is changed. The same happens for other hardware whose information is included in serial keys.

In order to retrieve hardware information, a developer should use Windows Management Instrumentation, or WMI. There are WMI system classes that can be utilized to get all properties of a motherboard, an HDD, and a CPU.

In the table below you can find WMI classes names for each type of hardware:

HDD Win32_DiskDrive
CPU Win32_Processor
Motherboard Win32_BaseBoard

.Net framework comes with the types from the namespace System.Management (at the same time, .Net core needs platform extensions to access to them). System.Management is easy to use API to work with WMI.

The ManagementClass class represents a particular WMI class. In order to get an instance for an HDD, use the following constructor:

ManagementClass mc = new ManagementClass("Win32_DiskDrive");

Once you have a ManagementClass instance, call GetInstances() to get available instances of the WMI class (each instance is represented by the ManagementObject class). For example, if there are several disk drives, GetInstances() help you get properties of each. As the very first instance is the system drive, it is enough to get the first ManagementObject returned by GetInstances(). ManagementObject provides properties through [] notation.

Let’s write a small application that displays all properties of a system drive:

var mc = new ManagementClass("Win32_DiskDrive");
var systemDrive = mc.GetInstances().Cast<ManagementBaseObject>().First();

Console.WriteLine(string.Join(Environment.NewLine,
    systemDrive.Properties.Cast<PropertyData>().Select(x => string.Format("Name: {0}, value: {1}", x.Name, x.Value))));

Here what I get on my machine:

Name: Availability, value: 
Name: BytesPerSector, value: 512
Name: Capabilities, value: System.UInt16[]
Name: CapabilityDescriptions, value: System.String[]
Name: Caption, value: HGST HDN784040ALE1280 SCSI Disk Device
Name: CompressionMethod, value: 
Name: ConfigManagerErrorCode, value: 0
Name: ConfigManagerUserConfig, value: False
Name: CreationClassName, value: Win32_DiskDrive
Name: DefaultBlockSize, value: 
Name: Description, value: 
Name: DeviceID, value: \\.\PHYSICALDRIVE2
Name: ErrorCleared, value: 
Name: ErrorDescription, value: 
Name: ErrorMethodology, value: 
Name: FirmwareRevision, value: BJVO
Name: Index, value: 2
...
Name: TotalCylinders, value: 486222
Name: TotalHeads, value: 255
Name: TotalSectors, value: 7814032065
Name: TotalTracks, value: 124032255
Name: TracksPerCylinder, value: 255

There is a lot of information we’ve got! But we need only some basic properties that should be available on all machines: Model, Manufacturer, Signature, and TotalHeads.

Let’s extend the code to outputs information for a motherboard and a CPU as well:

foreach (var managementClassName in new[] { "Win32_DiskDrive", "Win32_Processor", "Win32_BaseBoard" })
{ 
    var managementClass = new ManagementClass(managementClassName);
    var managementObject = managementClass.GetInstances().Cast<ManagementBaseObject>().First();

    Console.WriteLine(string.Format($"Management class name: {managementClassName}"));
    Console.WriteLine(string.Join(Environment.NewLine,
        managementObject.Properties.Cast<PropertyData>().Select(x => string.Format($"Name: {x.Name}, value: {x.Value}"))));
}

For the motherboard, we will use Model, Manufacturer, Name, and SerialNumber, as they should be available on all machines. For the CPU, it is a good idea to use UniqueId, but it’s possible that this property is not provided. In such a case, we can use ProcessorId, Name, or Manufacturer.

Well, now we have a set of hardware properties, but how to get a hardware id that is based on these properties? The simplest way is just to concatenate the values and to get a hash of the large string. Don’t forget to check whether the property is available. In order to simplify the code, create a separate method that yields hardware properties:

IEnumerable<string> GetHardwareProperties()
{ 
    foreach (var properties in new Dictionary<string, string[]>
    {
        { "Win32_DiskDrive", new[] { "Model", "Manufacturer", "Signature", "TotalHeads" } },
        { "Win32_Processor", new[] { "UniqueId", "ProcessorId", "Name", "Manufacturer" } },
        { "Win32_BaseBoard", new[] { "Model", "Manufacturer", "Name", "SerialNumber" } }
    })
    {
        var managementClass = new ManagementClass(properties.Key);
        var managementObject = managementClass.GetInstances().Cast<ManagementBaseObject>().First();

        foreach (var prop in properties.Value)
        { 
            if (null != managementObject[prop])
                yield return managementObject[prop].ToString();
        }
    }
}

var hash = (new SHA256Managed()).ComputeHash(Encoding.UTF8.GetBytes(string.Join("", GetHardwareProperties())));
var hardwareId = string.Join("", hash.Select(x => x.ToString("X2")));

Console.WriteLine($"Hardware id: {hardwareId}");

Wrapping up

Thanks to System.Management.ManagementClass it is easy to calculate hardware id in .Net. Hashing hardware properties gives a 256-bit value that can be used to issue a serial key, and to check if the serial key has been issued for a particular computer.

In practice, the method, that returns a hardware id, should be obfuscated to prevent it from modifications or removing.