Article Author: Joydip Kanjilal
Introduction
Node locking an application implies that the application can only be deployed in a system that contains a valid system-specific activation code. The same activation code cannot therefore be used on another system. The sample application that I’ll build in this article has two different components: A web application that would be executed at the deployed site and a Windows application that should be executed on the developer’s machine. The web application will generate a node ID for the system and store it in an encrypted format in a license file (a text file) on the file system of the deployed system. The Windows application will read this file, decode the node ID, generate an activation code and store it in the same license file. The web application can be used only if it can find a valid activation code within the license file. It is to be noted here that both the node ID and activation code are system specific. The node locking functionality that is illustrated in the web application can also be used within a Windows application. Remember that node locking functionality should be embedded in software that is being sold. If for example there be two companies A and B where A has purchased the web application from B, the node locking functionality should be part of the web application that company A has purchased and the company B would provide the activation code for the web application to the company A using the Windows application shown later in this article.
Precise meaning of some of the terminology used, including node ID, activation code, etc are discussed in more detail in the sections that follow.
The Node ID and Activation Code
For the purposes of this article, a node ID or an activation code is the name given to an encrypted combination of the CPU ID and the MAC ID of a system. In order to distinguish a node ID from the activation code, the node ID is prefixed with the letter "N" and the activation code with the letter "A" and a different password is used to encrypt and decrypt both. Those are the only differences between the node ID and the activation code. I only use the combination of CPU ID and MAC ID to generate these codes: Simply taking the MAC ID of the system and using encryption to generate the node ID or the activation code may have been enough but my aim here is to give a logical name to a unique id for a system that can be distinguished from the MAC ID of a system.
Components of a Node ID
The following are the main components that are used to create a node ID or an activation code:
- CPU ID
- MAC ID
The technology that I have used here to gather the necessary hardware information in the installation system is Windows Management Instrumentation (WMI). WMI is an extensible, high performance, object-oriented framework for accessing management-related information from a local or remote system. It encapsulates the internal functionalities and exposes a uniform API that is used to retrieve the necessary data. The WMI classes are available in the System.Management namespace in the .NET framework.
WMI encapsulates all the available information about your computer’s central processor, including model number, processor family, cache size, clock speed, and manufacturer codename. The Media Access Control or MAC address is the Network Interface Card address and is composed of 12 hexadecimal characters (0-9, A-F) and is defined as a unique identifier used to identify most network equipment and is used as the source and destination addresses for data packets on a LAN. Every network device in the world has a unique MAC address. The first 6 characters of the MAC address are unique to the manufacturer of the device.
System Requirements
To run the sample application for this article you should have:
- .NET Framework 1.1 or later
- Windows XP or 2000 or later
- IIS Web Server running
A good knowledge of WMI is preferred though it is not absolutely mandatory.
Installing and Compiling the Sample Code
In order to build the sample application, follow these simple steps:
- Unzip the download and you will find two folders, one for the web application and one for the Windows application.
- Create a NodeLocking virtual directory in IIS and point this to the folder containing the web project
- Open the Web Application provided with the source code in Visual Studio
- Add a reference to System.Management dll
- Create a folder where the license file would be stored and provide the necessary permissions to the same
- Compile and execute the web application
When you run the web application, the Message.aspx form is displayed, a node id is then generated and stored in the license file. The appropriate message is displayed in the label control of this form.
Next, follow these steps to create the Windows application to activate the application created above.
- Open the Windows Application provided with the source code
- Add a reference to the NodeLocking.dll file
- Compile and execute the application
Click on the button to generate the activation code. This activation code is generated by reading the node ID from the license file and using an encryption mechanism as stated in the Encryption class in the NodeLocking namespace. Re-start the web application to see that the Login form is displayed. Note that if both these applications are executed on the same system you need not to port the license file. Otherwise you have copy the license file to and from the deployment site for activation of the software.
How the Node Locking Application Works
Figure 1 provides a flow chart showing how the node locking application works. Recall that Windows application is used to generate the activation code for the web application and it is not meant to be shipped with the application at the deployment site. Further, here site refers to a system, i.e., we have a deployment system and a system on which the activation code is generated. I will be referring to them as the deployment and the development sites from now on.

Figure 1. Flowchart showing how the application works
The web application first checks to see whether a license file exists. A license file is one that contains either the node ID or the activation code for the web application. If the license file is not found then a new license file is created using the procedure as mentioned below.
The application retrieves the MAC ID and the CPU ID from the system on which the application is installed / deployed. This combination is used to produce the node ID, which will be unique for a particular system. The node ID is then encrypted and stored in the license file, which has the extension, .lic. I have used the Rijndael encryption algorithm in the sample code, but you can if you prefer modify the source code to use other encryption methods. You can also use obfuscation to ensure that the application cannot be dissembled and hence ensure a better code protection mechanism.
The license file is shipped to the deployed site for generating the activation code for the particular system it is being installed on. It will also be used at the developer site to decode the same and get the decrypted node ID. The activation code is then generated at the development site using a different password from the one that was used previously. This activation code is stored in the license file once again and the previously stored node ID in the license file is replaced with it.
Figure 2 shows what the user will see when installing the application.

Figure 2. Generating the node ID
As shown in Figure 2, the login screen for the application does not appear when the application is executed for the first time after deployment. Rather, a message is displayed informing the user of the creation and storage of node ID in the license file, as shown in Figure 3. Figure 3 and Figure 4 show the license file being selected at the development site using the Open button on the form; this pops up a file open dialog box and the activation code as discussed earlier is generated and stored in the license file.

Figure 3. Selecting a license file from the Windows application

Figure 4. Confirmation that an activation code has been saved to the license file
The license file is then sent back to the deployed site in order to allow the application to run. The product will be activated once the application is re-started with the new license file as shown in Figure 5.

Figure 5. The application has been activated and presents the login form.
Figure 6 shows the message that is displayed if the license file is available but contains an invalid activation code.

Figure 6. An example of accessing an application that has not been activated
To give you an idea, I’ve listed belowthe sample node ID and activation code as generated in my system.
Sample node ID:
NkVP5a2vTpvY/MY7qWMrGPd2u3eRO44jc25E3GMpkrKQtcR0piYC+rUNj5X9khtzNd8Vwy
UPEK1unnjcsY4s0yR7keRPkWrtNt0WXjOCYcxs=
Sample activation code:
A6uh5vHjT8OiVRxhbtDzE/1ZD6jfwKyRU/hCfoHOzjMdAcXV1S/yt99jRQshQRt/1vSXh
CrmEYNLdfwSLjkU62KQZj6agOkQ2e4JurV5NqUU=
Implementing the Node-Locking Application
Let’s first start with the application that would be deployed at the client’s end. For the sake of simplicity, let’s consider that both the web application and the Windows application are on the same system.
The Web application is comprised of the following main files:
- Login.aspx.cs
- Encryption.cs
- NodeLocking.cs
- Logger.cs
- PageBase.cs
- Message.aspx.cs
The Login.aspx.cs file contains the code to activate the software. If the license file is not available at the desired location, it creates a new one and stores the node ID inside it.
The login page contains two text boxes for entering the user id and the password and a submit button. The login page is shown only if the application has been activated (the license file contains a valid activation code). When there is no license file, a new one is created and the encrypted node ID is stored in it. If the license file contains an invalid activation code, the login page is not displayed and in both these cases, the flow of control is redirected to the Message.aspx page and appropriate message is displayed to the user. The license file that contains the encrypted node ID is used by the Windows application at the development site to generate the activation code for the application.
The Encryption.cs file contains the encryption that is responsible for encrypting and decrypting text based on a specific password. The class contains the overloaded Encrypt() and Decrypt() methods that make use of the Rijndael’s algorithm (see the Related Links section for more information) to accomplish the task of encryption and decryption. The code for the class is as shown below:
namespace NodeLocking
{ internal sealed class Encryption { private Encryption() { } public static string Encrypt(string str, string password) { byte[] buffer; buffer = Encoding.Unicode.GetBytes(str); PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[] {0×51, 0×76, 0×61, 0×6e, 0×20, 0×4d, 0×65, 0×64, 0×76, 0×65, 0×64, 0×65, 0×76}); return Convert.ToBase64String(Encrypt(buffer, pdb.GetBytes(32), pdb.GetBytes(16))); } public static string Decrypt(string str, string password) { byte[] buffer; buffer = Convert.FromBase64String(str); PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new byte[] {0×51, 0×76, 0×61, 0×6e, 0×20, 0×4d, 0×65, 0×64, 0×76, 0×65, 0×64, 0×65, 0×76}); return Encoding.Unicode.GetString(Decrypt(buffer, pdb.GetBytes(32), pdb.GetBytes(16))); } private static byte[] Encrypt(byte[] byteArrayToEncrypt, byte[] Key, byte[] IV) { MemoryStream memoryStream = new MemoryStream(); Rijndael rijndael = Rijndael.Create(); rijndael.Key = Key; rijndael.IV = IV; CryptoStream cryptoStream; cryptoStream = new CryptoStream(memoryStream, rijndael.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(byteArrayToEncrypt, 0, byteArrayToEncrypt.Length); cryptoStream.Close(); byte[] returnArray = memoryStream.ToArray(); memoryStream.Close(); return returnArray; } private static byte[] Decrypt(byte[] byteArrayToEncrypt, byte[] Key, byte[] IV) { MemoryStream memoryStream = new MemoryStream(); Rijndael rijndael = Rijndael.Create(); rijndael.Key = Key; rijndael.IV = IV; CryptoStream cryptoStream; cryptoStream = new CryptoStream(memoryStream, rijndael.CreateDecryptor(), CryptoStreamMode.Write); cryptoStream.Write(byteArrayToEncrypt, 0, byteArrayToEncrypt.Length); cryptoStream.Close(); byte[] returnArray = memoryStream.ToArray(); memoryStream.Close(); return returnArray; } }
}
The class is sealed to prevent further inheritance and contains a private constructor and static methods. The private constructor is used to ensure that the class cannot be instantiated. The rest of the code is quite simple, let’s move on further to the next file.
The NodeLocking class contains the methods to generate the nodeID and the activation code as well as some helper methods like GetMACAddress() and GetProcessorID(). These methods call the Encrypt() and the Decrypt() methods of the Encryption class shown previously. The method names indicate what the intended purposes of the methods are.
namespace NodeLocking
{ internal sealed class NodeLocking { private NodeLocking() { } public static string GetMACAddress() { ManagementClass managementClass = new ManagementClass("Win32_NetworkAdapterConfiguration"); ManagementObjectCollection managementObjectCollection = managementClass.GetInstances(); string MACAddress=String.Empty; foreach(ManagementObject managementObject in managementObjectCollection) { if(MACAddress==String.Empty) { if((bool)managementObject["IPEnabled"] == true) MACAddress = managementObject["MacAddress"].ToString() ; } managementObject.Dispose(); } MACAddress=MACAddress.Replace(":",""); return MACAddress; } public static string GetProcessorID() { string cpuInfo = String.Empty; ManagementClass managementClass = new ManagementClass("Win32_Processor"); ManagementObjectCollection managementObjectCollection = managementClass.GetInstances(); foreach(ManagementObject managementObject in managementObjectCollection) { if(cpuInfo.Equals(String.Empty)) { cpuInfo = managementObject.Properties["ProcessorId"]. Value.ToString(); } } return cpuInfo; } private static string GetProcessorID(string password) { return Encryption.Encrypt(GetProcessorID(),password); } private static string GetMACAddress(string password) { return Encryption.Encrypt(GetMACAddress(),password); } public static string GetEncryptedNodeId(string password) { return "N"+Encryption.Encrypt(GetNodeId(),password); } public static string GetDecryptedNodeId(string encryptedNodeId,string password) { return Encryption.Decrypt(encryptedNodeId,password); } private static string GetNodeId() { return GetProcessorID()+"*"+GetMACAddress(); } public static string GetActivationCode(string nodeId,string password) { return "A"+Encryption.Encrypt(nodeId,password); } public static bool ValidateNodeId(string nodeId) { if(GetNodeId().Equals (nodeId)) return true; return false; } public static bool ValidateActivationCode(string activationCode) { if(activationCode.Trim().Equals(GetActivationCode (GetNodeId(),"ActCode"))) return true; return false; } } }
This class is also sealed and contains a private constructor. Both these classes (NodeLocking and Encryption) are internal to the assembly in which they are contained i.e., they cannot be used in any other assembly. The next to be shown is the Logger class contained in the Logger.cs file that reads and writes a given string to and from a text file. The necessary synchronization mechanism is implemented in the Write() method of this class to ensure thread safety.
namespace NodeLocking
{ public sealed class Logger { private static readonly object lockFile = new object(); private string fileName = String.Empty; public string FileName { get { return fileName; } set { fileName = value; } } public void Write(string str) { try { Monitor.Enter(lockFile); StreamWriter sw = File.CreateText(this.FileName); sw.Write(str); sw.Flush(); sw.Close(); } catch (Exception e) { throw e; } finally { Monitor.Exit(lockFile); // Release lock. } } public string Read() { StreamReader sr = File.OpenText(this.FileName); string returnString = sr.ReadLine(); sr.Close(); return returnString; } }
}
The NodeLocking functionality is called in the web application using an abstract class that extends the Page class of the System.Web.UI namespace to ensure reusability.
The following is the code for the PageBase class that can be extended by any other page in the application to test whether the license file is valid.
namespace NodeLocking
{ public abstract class PageBase : System.Web.UI.Page { public PageBase() { int returnValue = ValidateActivation(); HttpContext.Current.Application.Remove("License"); HttpContext.Current.Application.Add("License",returnValue); if(returnValue != 1) HttpContext.Current.Response.Redirect("Message.aspx",true); } public static int ValidateActivation() { Logger log = new Logger(); log.FileName = ConfigurationSettings.AppSettings["LicenseFileName"]; System.IO.FileInfo fileInfo = new System.IO.FileInfo(log.FileName); if(fileInfo.Exists) { string actCode = log.Read().Trim(); if(!NodeLocking.ValidateActivationCode(actCode)) { return -1; } else { return 1; } } else { log.Write(NodeLocking.GetEncryptedNodeId("NodeID")); return 0; } } }
}
The ValidateActivation() method returns 0, -1 or 1. A return value of 0 implies that there is no license file present and the node ID should be generated and stored in a new license file. A return value of -1 indicates that the activation code is invalid while a return value of 1 implies that the license file contains a valid activation code for the system. These values (0,-1,1) are stored in the application state in the constructor of this abstract class.
The Message.aspx file is the last file in the sample application and it retrieves this value from the application state to display appropriate messages to the end user. The code for the Message.aspx.cs file is as shown below:
namespace NodeLocking
{ public class Message : System.Web.UI.Page { protected System.Web.UI.WebControls.Label lblMessage; private void Page_Load(object sender, System.EventArgs e) { int licenseKeyValue = int.Parse(Application["License"].ToString()); switch(licenseKeyValue) { case -1: lblMessage.Text = "The Application Is Not Yet Activated"; break; case 0: lblMessage.Text = "Node ID Generated And Stored In The License File"; break; } } override protected void OnInit(EventArgs e) { InitializeComponent(); base.OnInit(e); } private void InitializeComponent() { this.Load += new System.EventHandler(this.Page_Load); } }
}
The PageBase class is inherited by the Login page of the web application. This completes the logic used in the web application.
Generating the Activation Code
In order to implement the node locking Windows Application, you need to remember to add a reference to the NodeLocking.dll created in the web application previously. The source code for the node locking Windows application is as shown below:
namespace NodeLockingApplication
{ public class Activate : System.Windows.Forms.Form { private System.Windows.Forms.Button btnActivate; private Logger logger = new Logger(); private System.Windows.Forms.OpenFileDialog openFileDialog; private System.Windows.Forms.TextBox txtOpenFile; private System.Windows.Forms.Button btnOpen; private System.Windows.Forms.Label lblSelectFile; private System.ComponentModel.Container components = null; public Activate() { InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } private void InitializeComponent() { //Usual code } [STAThread] static void Main() { Application.Run(new Activate()); } private void frmActivate_Load(object sender, System.EventArgs e) { } private void btnActivate_Click(object sender, System.EventArgs e) { if(txtOpenFile.Text.Trim().Equals(String.Empty)) { MessageBox.Show("Please select license file","Node Locking"); return; } logger.FileName = txtOpenFile.Text; FileInfo thisFile = new FileInfo(logger.FileName); if(thisFile.Exists) { if(logger.Read().Trim().StartsWith("N")) { string encryptedNodeID = logger.Read(); encryptedNodeID = encryptedNodeID.Substring(1,logger.Read(). Trim().Length – 1); string nodeID = NodeLocking.NodeLocking. GetDecryptedNodeId(encryptedNodeID,"NodeID"); string activationCode = NodeLocking.NodeLocking. GetActivationCode(nodeID,"ActCode"); logger.Write(activationCode); MessageBox.Show("Activation Code generated…","Node Locking"); } else if(logger.Read().Trim().StartsWith("A")) MessageBox.Show("License file is activated.."); else MessageBox.Show("Invalid Node Id/format in license file","Node Locking"); } else MessageBox.Show("License file not found.."); } private void btnOpen_Click(object sender, System.EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.Title = "Open File"; openFileDialog.Filter = "All files (.)|*.*"; openFileDialog.FilterIndex = 0; openFileDialog.RestoreDirectory = true ; if(openFileDialog.ShowDialog() == DialogResult.OK) { txtOpenFile.Text = openFileDialog.FileName ; } }
}
Note how the node ID is decrypted and the activation code is generated and then stored in the same license file.
The last point in our discussion is the web.config file. The name of the license file is stored in it so that if the license file is relocated, you will not need to change the source code of the web application. The appSettings section of the web.config file is as given below:
<appSettings>
<add key = "LicenseFileName" value = "C:Test.lic">
</add>
</appSettings>
Note the key LicenseFileName and its corresponding value C:\Test.lic. This key will be used as the path to the license file for the web application and this value can be changed as required.
Conclusion
In this article, I have implemented a node locking application with all the relevant code to illustrate how you can use node-locking to copy protect your own application.

