Hardware ID Locking in .NET: Binding a License to a Machine
Hardware ID locking binds a software license to the specific physical machine on which it was activated. A licensed user cannot install the same single-seat license on a second machine, share their license key with a colleague, or copy the license file to another computer. For commercial ISV products, it is the simplest mechanism that prevents the most common casual piracy scenario: a paying customer forwarding their serial key to someone who has not paid.
The enforcement mechanism is a hardware fingerprint - a hash derived from identifiers of physical components in the machine. The fingerprint is stored as part of the license and verified on each application launch. If the fingerprint does not match the current machine, the license is rejected.
It does not require a server connection. It does not require internet access after activation. It works for desktop applications in air-gapped environments. For many ISV products, HWID locking is the foundational licensing control that everything else is built on.
Generating a hardware fingerprint in C#
The first decision is which hardware components to include. On Windows, the standard approach uses Windows Management Instrumentation to query hardware properties:
using System.Management;
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();
}
}
}This collects properties from three hardware classes: the disk drive, the CPU, and the motherboard. The collected values are concatenated and hashed to produce a single fingerprint string:
using System.Security.Cryptography;
var hash = SHA256.HashData(
Encoding.UTF8.GetBytes(string.Join("", GetHardwareProperties())));
var hardwareId = string.Join("", hash.Select(x => x.ToString("X2")));The result is a deterministic string that should be the same on every run on the same machine and different on different machines. The System.Management namespace provides access to WMI - in modern .NET, add the System.Management NuGet package to your project.
For a complete walkthrough with source code: How to write hardware ID generator →
The fingerprint stability problem
The code above generates a working fingerprint. The harder problem is keeping it working when legitimate customers change their hardware.
Every component in the fingerprint is a component that can change. A customer replaces a failing hard drive and the disk serial number changes. A customer upgrades their CPU and the processor ID changes. A customer's motherboard fails under warranty and the replacement has a different serial number. In each case, the hardware fingerprint changes and the application rejects the license on the machine that was previously activated.
If the fingerprint requires exact identity of all components, legitimate customers get locked out after routine hardware changes. Support tickets accumulate. Customer frustration grows. The developer ends up manually issuing replacement license keys for customers who have done nothing wrong.
One mitigation is a scored approach: hash each hardware attribute independently, store them as a set rather than a single hash, and require a minimum number to match rather than all of them. For example, track six attributes and require at least four to match. A customer who replaces one or two components still passes validation. A customer on a completely different machine fails.
This design has its own tradeoffs. What threshold is safe? How do you handle gradual replacement where a customer changes one component per month until the machine is entirely different? How do you store the individual attribute hashes securely so an attacker cannot forge them? Each of these is a design decision the developer now owns.
The cross-platform problem
The WMI approach works on Windows. It does not work on Linux or macOS.
On Linux, hardware information is available through different mechanisms: DMI data via dmidecode (requires root), CPU information from /proc/cpuinfo, disk serial numbers from /dev/disk/by-id/. On macOS, the IOKit framework provides hardware identifiers, accessible via P/Invoke or a wrapper library.
A developer building a cross-platform .NET application that needs HWID locking has to implement and test separate fingerprint collection paths for each platform, each with its own quirks around permissions, availability, and stability of the identifiers. The fingerprint code that took a day to write for Windows becomes a week of platform-specific work with platform-specific edge cases.
What the fingerprint code alone does not solve
Generating a reliable hardware fingerprint is one part of the problem. A production-quality HWID licensing system also requires:
A license file format that embeds the hardware fingerprint alongside the license metadata (user name, expiration date, feature flags) in a tamper-resistant structure. If the license file is a plain-text file that contains the HWID hash, an attacker replaces the hash with their own machine's fingerprint and the check passes.
An activation flow that collects the customer's hardware ID, transmits it to wherever license keys are generated, and returns a bound license. For local activation this means the key generator must accept a hardware ID as input and embed it in the signed license. For server-based activation, an activation endpoint is needed.
A reactivation mechanism for customers whose hardware changes. If the HWID check is strict, every hardware change requires a new license key. The developer needs a policy (how many reactivations per license?) and an implementation (a reactivation endpoint, or manual key reissue through support).
Tamper resistance for the validation logic itself. In an unobfuscated binary, the HWID validation routine is the first thing an attacker targets. Using dnSpy, the validation method is findable, readable, and patchable in minutes - change the branch that returns false on mismatch to always return true, and the HWID check is defeated regardless of how sophisticated the fingerprinting algorithm was.
ArmDot's built-in hardware ID locking
ArmDot provides hardware ID locking as a built-in capability of its licensing system. The fingerprint generation, the license key format, the activation validation, and the tamper resistance are all handled internally.
Generate a hardware ID for the current machine:
string hwid = ArmDot.Client.Api.GetCurrentMachineHardwareId();Or from the command line:
ArmDotConsole --generate-hardware-id --hardware cpu,hdd,motherboardThe --hardware parameter controls which components are included in the fingerprint. Any combination of cpu, hdd, and motherboard can be specified.
When generating a license key (via ArmDot UI or ArmDotConsole), the hardware ID is embedded in the key using asymmetric encryption. The key is cryptographically signed - an attacker cannot modify the embedded hardware ID without invalidating the signature.
Validating the license in the application:
[ArmDot.Client.VirtualizeCode]
private void CheckLicense(string key)
{
ArmDot.Client.Api.PutKey(key);
switch (ArmDot.Client.Api.GetLicenseState())
{
case ArmDot.Client.Api.LicenseKeyState.Valid:
// License is valid and HWID matches
break;
case ArmDot.Client.Api.LicenseKeyState.BadHardwareId:
// HWID mismatch - wrong machine
break;
case ArmDot.Client.Api.LicenseKeyState.Expired:
// License expired
break;
case ArmDot.Client.Api.LicenseKeyState.Invalid:
// Invalid key
break;
}
}The [VirtualizeCode] attribute on the validation method is deliberate. The license check is the highest-value target for an attacker - converting it to custom bytecode that no decompiler can reconstruct makes the validation logic resistant to the patch-the-branch attack described above.
ArmDot's HWID is currently Windows-only, using the same WMI-based approach. The hardware ID changes if the selected hardware components change - there is no built-in fuzzy matching. For customers who replace hardware, a new license key with the updated hardware ID is needed. The licensing workflow (ArmDot UI or ArmDotConsole) makes generating replacement keys straightforward.
Why HWID locking and obfuscation are not separate concerns
A robust HWID scheme in a readable binary gives an attacker a clean target. The validation method is visible in the decompiled source. The comparison logic is obvious. The WMI query strings, the error messages shown to the user, and the readable method names guide the attacker directly to the right method. Patching takes minutes.
The same HWID scheme in an obfuscated binary is a different proposition. String encryption hides the WMI class names, property strings, and error messages that would otherwise guide an attacker directly to the validation method. Control flow obfuscation makes the validation method harder to trace. Code virtualization on the license check replaces it with a VM dispatch that resists static analysis entirely.
HWID locking and obfuscation are not alternatives - they are layers. The fingerprint establishes the binding. The obfuscation protects the binding from being removed.
Related: String Encryption for .NET → - protecting the WMI class names and license strings that would otherwise guide an attacker to the validation logic.
Related: WPF and WinForms Obfuscation → - desktop ISV applications are the primary use case for HWID locking.
Back to: .NET Licensing Protection →
ArmDot licensing
ArmDot provides hardware ID locking, serial key generation, and license validation as built-in features alongside its obfuscation capabilities. The licensing API is part of the same ArmDot.Client NuGet package used for obfuscation - no additional dependencies. Free trial available - protected assemblies stop working after two weeks.
