# VBForums CodeBank > CodeBank - C# >  C# - PC fingerprint (for program piracy protection)

## BrianHawley

The following code returns an 8 digit number that is the 'fingerprint' of the hardware of the PC it is running on.

Note that this is *C#*  code and will not run on VB. Will probably adapt to VB.NET ok (any volunteers?) 

Call with something like:



```
Fingerprint fp = new Fingerprint();
MessageBox.Show(fp.Value());
```

You can play around with what you test. This example tests quite 
a few things and might be too slow for some uses - but testing 
fewer things is less secure.

Note that not all PC's have all items. e.g. may not have network 
card. Will not hang in this case as returns default values. Older 
PC's may not have things like unique CPU or motherboard serial 
numbers, so don't rely on this.

Normal use for this type of application is to generate 'activation' 
codes for software. Customer sends you his fingerprint and you
send him a matching code to activate the software. If he posts 
the code on the Internet, you don't need to worry too much as it 
will only run on machines with the same fingerprint.


Advantages: more secure than universally reuseable activation 
code, which will soon be on the serialz sites. Cheaper than a 
dongle and nothing to ship.

Disadvantage: If legit user changes hardware then needs a new 
code. May disuade/irritate some purchasers. (Best used with a 
website where users can generate their own reactivation codes 
to be sent only to their registered email address - and kill that 
address if it generates an unreasonable number requests on the 
same serial number. If app is guaranteed Internet access - e.g. 
it's a browser or an FTP tool - then this process can be invisible to 
the customer - just do it in background.)



```
using System;
using System.Globalization;
using System.Threading;
using System.Resources;



using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Reflection;
using System.Text;


namespace Whatever
{

	public class Fingerprint
		//Fingerprints the hardware
	{
		public string Value()
		{
			return pack(cpuId()
				 + biosId()
				 + diskId()
				 + baseId()
				 + videoId()
				 + macId());
		}
		private string identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue)
			//Return a hardware identifier
		{
			string result="";
			System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
			System.Management.ManagementObjectCollection moc = mc.GetInstances();
			foreach(System.Management.ManagementObject mo in moc)
			{
				if(mo[wmiMustBeTrue].ToString()=="True")
				{

					//Only get the first one
					if (result=="")
					{
						try
						{
							result = mo[wmiProperty].ToString();
							break;
						}
						catch
						{
						}
					}

				}
			}
			return result;
		}
		private string identifier(string wmiClass, string wmiProperty)
			//Return a hardware identifier
		{
			string result="";
			System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
			System.Management.ManagementObjectCollection moc = mc.GetInstances();
			foreach(System.Management.ManagementObject mo in moc)
			{

				//Only get the first one
				if (result=="")
				{
					try
					{
						result = mo[wmiProperty].ToString();
						break;
					}
					catch
					{
					}
				}

			}
			return result;
		}

		private string cpuId()
		{
			//Uses first CPU identifier available in order of preference
			//Don't get all identifiers, as very time consuming
			string retVal = identifier("Win32_Processor","UniqueId");
			if (retVal=="") //If no UniqueID, use ProcessorID
			{
				retVal = identifier("Win32_Processor","ProcessorId");
			
				if (retVal=="") //If no ProcessorId, use Name
				{
					retVal = identifier("Win32_Processor","Name");


					if (retVal=="") //If no Name, use Manufacturer
					{
						retVal = identifier("Win32_Processor","Manufacturer");
					}

					//Add clock speed for extra security
					retVal +=identifier("Win32_Processor","MaxClockSpeed");
				}
			}

			return retVal;

		}
		private string biosId()
			//BIOS Identifier
		{
			return identifier("Win32_BIOS","Manufacturer")
			+ identifier("Win32_BIOS","SMBIOSBIOSVersion")
			+ identifier("Win32_BIOS","IdentificationCode")
			+ identifier("Win32_BIOS","SerialNumber")
			+ identifier("Win32_BIOS","ReleaseDate")
			+ identifier("Win32_BIOS","Version");
		}		
		private string diskId()
			//Main physical hard drive ID
		{
			return identifier("Win32_DiskDrive","Model")
			+ identifier("Win32_DiskDrive","Manufacturer")
			+ identifier("Win32_DiskDrive","Signature")
			+ identifier("Win32_DiskDrive","TotalHeads");
		}		
		private string baseId()
			//Motherboard ID
		{
			return identifier("Win32_BaseBoard","Model")
			+ identifier("Win32_BaseBoard","Manufacturer")
			+ identifier("Win32_BaseBoard","Name")
			+ identifier("Win32_BaseBoard","SerialNumber");
		}		
		private string videoId()
			//Primary video controller ID
		{
			return identifier("Win32_VideoController","DriverVersion")
			+ identifier("Win32_VideoController","Name");
		}
		private string macId()
			//First enabled network card ID
		{
			return identifier("Win32_NetworkAdapterConfiguration","MACAddress", "IPEnabled");
		}
		private string pack(string text)
			//Packs the string to 8 digits
		{
			string retVal;
			int x = 0;
			int y = 0;
			foreach(char n in text)
			{ 
				y ++;
				x += (n*y);
			}

			retVal = x.ToString() + "00000000";

			return retVal.Substring(0,8);
		}

	}
}
```

----------


## ßädbö¥

Hey.... BrianHawley .....
I think this is very lenthy process...  :Frown:   :Frown:  
The easier 1 is..... :Big Grin:   Just detect the the serial no of HDD and just check serial no in ur programme. If serial no matches then allow user to use the software.... I think this is the easy way....
What do u think  :Confused:

----------


## jacsoft

Well this is not handy. I use your software (both ideas) and then my computer needs to be formatted. Hey that's not nice I get a new HDD serial. Hmm, now both of your codes are not usefull anymore.

----------


## mrcc

im not gonna mention any names *coughs* volid *coughs* but it changes ur hard drive serial # So whats the point doing it that way? Theres no easy way to do it. So that code is useless.

----------


## ßädbö¥

*No problem....!!
If user format his HDD and serial # get changed then he've to send new serial no to a programmer who made the software. and programmer have to send his new password according to his serial # if the user has a legal copy of the software...!

Just put a code which shows the Serial # of HDD when user enters a worg password....

belive me this is the best idea and i've used this technique in on of my programme...
Just think abou it....
 *

----------


## BrianHawley

Only checking the hard-drive serial number is not very secure as it can be changed. It's only included as a fall-back because some PC's do not have individual CPU serials etc. However only a certain proportion of your users will know how to do this, so there is still some security in this method.

If you change something on the PC, then yes, you will need a new unlock code - which is why this mehtod is best if there is a website where users can generate codes without calling you.

If you want to get fancy, you could mod the code so that a user can change any one thing on the PC at a time without breaking the lock. i.e. recalculate and accept the fingerprint for everything, so long as a few things remained the same.

Problem with any type of software locking is that user convenience and program security lie in opposite directions, so it is always a compromise.

----------


## DiGiTaIErRoR

Dongles aren't even secure.

 :Stick Out Tongue:

----------


## BrianHawley

No they are not 100% secure.

They can vary between almost no security and 99.99% depending on how they are used.

1) You must use a dongle with an ASIC (Application specific IC) chip. ASIC means that the manufacturer makes or has made a special chip, so nobody can buy the chip. Some cheap dongles have off-the shelf EPROMS that any idiot can clone with standard parts and an EPROM writer.

2) You must use a dongle with an algorythm that is not crackable in a practical time with present technology. (The algorythm sends back an encoded reply in response to a string sent to it.)

3) You must use the algorythm by sending ten or twenty different strings at random times and checking you get the expected replies. NOT just check that the dongle is there.

4) You must also periodically send random strings to the dongle, where you ignore the reply. This is to defeat dongle breakers that simply record the traffic between the PC and dongle, then clone the right response. This method overloads them, or fills up the drive with data.

5) Usual anti-hack rules apply. i.e. call the dongle from many places in the program, not just one. Keep security code isolated and encapsulated in different places - don't just call the same routine from different places. Checksum the EXE using a non-standard method. Have several layers of security that cut in at different and increasing time intervals - hacker breaks the daily one and sells your code, then the weekly one cuts in - he breaks that then the monthly one cuts in etc. etc. 

6) If the protection trips, don't use obvious messages. Hackers search for them. Use a generic error routine you call from many places. Will take a lot of time for a hacker to back-track them all. Also a generic error message might make the customer call your help desk. He may not know he has a pirated copy and you might get a sale or catch a pirate.   

7) Don't use dongle makers wizards to apply protection. Write your own.

8) Challenge a good hacker you trust to break it! If he/she succeeds, identify the weak points.

Dongles can get pretty near 100% if you use them properly - but I've seen  'dongle protected' programs that could be hacked in a few hours because they did not follow the above rules.

We have been selling dongle-protected software for ten years and have never ever had a single security break.

Remember that you are not trying to build a hack-proof program. That's impossible. You are trying to make it easier for a hacker to write a look-alike of your program from scratch than to break the protection.

You might also be interested to know that as well as dongles, Rainbow make an almost identical hardware device that is used by the US Military for data-encryption - so the technology is pretty good if used correctly.

----------


## SolutionDeveloper

For you, guys ...



```
Imports System
Imports System.Globalization
Imports System.Threading
Imports System.Resources
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Data
Imports System.Reflection
Imports System.Text

Public Class Fingerprint

  Public Function Value() As String
    Return pack(cpuId() + biosId() + diskId() + baseId() + videoId() + macId())
  End Function



  Private Function identifier(ByVal wmiClass As String, ByVal wmiProperty As String, ByVal wmiMustBeTrue As String) As String
    Dim result As String = ""
    Dim mc As System.Management.ManagementClass = New System.Management.ManagementClass(wmiClass)
    Dim moc As System.Management.ManagementObjectCollection = mc.GetInstances()
    Dim mo As System.Management.ManagementObject
    For Each mo In moc
      If mo(wmiMustBeTrue).ToString() = "True" Then
        If result = "" Then
          Try
            result = mo(wmiProperty).ToString()
            Exit For
          Catch

          End Try
        End If

      End If
    Next
    Return result
  End Function

  Private Function identifier(ByVal wmiClass As String, ByVal wmiProperty As String) As String
    Dim result As String = ""
    Dim mc As System.Management.ManagementClass = New System.Management.ManagementClass(wmiClass)
    Dim moc As System.Management.ManagementObjectCollection = mc.GetInstances()

    For Each mo As System.Management.ManagementObject In moc

      If (result = "") Then
        Try
          result = mo(wmiProperty).ToString()
        Catch
        End Try
      End If
    Next
    Return result
  End Function

  Private Function cpuId() As String
    'Uses first CPU identifier available in order of preference
    'Don't get all identifiers, as very time consuming
    Dim retVal As String = identifier("Win32_Processor", "UniqueId")
    If retVal = "" Then
      retVal = identifier("Win32_Processor", "ProcessorId")

      If retVal = "" Then
        retVal = identifier("Win32_Processor", "Name")


        If retVal = "" Then
          retVal = identifier("Win32_Processor", "Manufacturer")
        End If

        'Add clock speed for extra security
        retVal += identifier("Win32_Processor", "MaxClockSpeed")
      End If
    End If

    Return retVal

  End Function

  Private Function biosId() As String
    Return identifier("Win32_BIOS", "Manufacturer") + identifier("Win32_BIOS", "SMBIOSBIOSVersion") + identifier("Win32_BIOS", "IdentificationCode") + identifier("Win32_BIOS", "SerialNumber") + identifier("Win32_BIOS", "ReleaseDate") + identifier("Win32_BIOS", "Version")
  End Function

  Private Function diskId() As String
    Return identifier("Win32_DiskDrive", "Model") + identifier("Win32_DiskDrive", "Manufacturer") + identifier("Win32_DiskDrive", "Signature") + identifier("Win32_DiskDrive", "TotalHeads")
  End Function

  Private Function baseId() As String
    Return identifier("Win32_BaseBoard", "Model") + identifier("Win32_BaseBoard", "Manufacturer") + identifier("Win32_BaseBoard", "Name") + identifier("Win32_BaseBoard", "SerialNumber")
  End Function

  Private Function videoId() As String
    Return identifier("Win32_VideoController", "DriverVersion") + identifier("Win32_VideoController", "Name")
  End Function


  Private Function macId() As String
    Return identifier("Win32_NetworkAdapterConfiguration", "MACAddress", "IPEnabled")
  End Function


  Private Function pack(ByVal text As String) As String
    Dim retVal As String
    Dim x As Integer = 0
    Dim y As Integer = 0
    Dim n As Char
    For Each n In text
      y += 1
      x += (AscW(n) * y)
    Next

    retVal = x.ToString() + "00000000"

    Return retVal.Substring(0, 8)
  End Function
End Class
```


My only problem is this bold marked line. VB.net does not allow to multiply a character with an integer. So what happend, if you do so in C#? Is my solution by creating an Ascii value ok?

SolutionDeveloper
MCSD

----------


## BrianHawley

Cool!

----------


## SolutionDeveloper

> Cool!



Thx, Brian.

But my question is still open   :Alien Frog:  

*What happens if you multiply a char value within C#?* 

Rgds
SolutionDeveloper

----------


## BrianHawley

The char is implicitly converted to its ascii value before being multiplied. 

so...



```
   {
      int x = 2;
      int y = 0;
      string s = "abc";
      char c = s[1];

      y = c * x;


    }
```

...works okay.

But there are people who will tell you is bad practice to rely on implicit conversion.

Hope this helps.

----------


## SolutionDeveloper

Yes, it does. It's excatly what my routine does as well...

Rgds
SolutionDeveloper

----------


## BrianHawley

Maybe the behaviour is a little less obvious once we get into Unicode strings, so perhaps better to convert explicitly as the purists recommend. I generally avoid implicit conversion, but that's more to do with readability.

----------


## wossname

Brian, where did you find these strings:

"Win32_BIOS", "Win32_DiskDrive", "Win32_Processor" and so on...?

I can't find any list of these in MSDN.

What others are available?

----------


## BrianHawley

> Brian, where did you find these strings:
> 
> "Win32_BIOS", "Win32_DiskDrive", "Win32_Processor" and so on...?
> 
> I can't find any list of these in MSDN.
> 
> What others are available?


It is in MSDN. I'm guessing you have an MSDN content filter turned on.

Here is an on-line verison:

http://msdn.microsoft.com/library/de...win32_bios.asp 

Hope that helps!

----------


## Kasracer

That method also uses clock speed? So if I over clock my CPU by 1MHz, your software will no longer work? That is definately not a good idea. Writing security code like this in .Net is not a good idea either since it can be disassembled easily.

The best idea I've come accrossed is to have the person setup a username and password on your server(s). Then, when they purchase your software, attach it to the username, then make them login when they use the software. Just don't allow more than two connections at the sametime. It's a very good scheme if the user has an always on connection or requires it for your program, otherwise it could be cumbersom but you could always have an off-line mode where it verifies that it's a legal copy once every few days or so just by login in and checking if anyone else is using it.

----------


## wossname

Yep, nice one  :Thumb:

----------


## wossname

> That method also uses clock speed? So if I over clock my CPU by 1MHz, your software will no longer work?


I don't think it works that way.  I reckon it would take the number that is hardwired into the CPU at the factory.  Say its a 3ghz when it leaves the factory, and you clock it up to 3.5ghz I think it would still return 3ghz.

But I agree that this fingerprint should not be based upon customizable things like Ram, framework version and so on because they do not affect the validity of the software.  Companies upgrade their hardware all the time they don't want to pay for new software every time they stick another gig of ram in their workstation.  HDD serial number would be an exception of course.

----------


## taco-man

> I don't think it works that way.  I reckon it would take the number that is hardwired into the CPU at the factory.  Say its a 3ghz when it leaves the factory, and you clock it up to 3.5ghz I think it would still return 3ghz.


If it gets it the from the same place windows does that is shown by going to properties in my computer then if you change the clock speed then it will show a different clock speed. you can even get a processor to post as a different one that what you have by simply over/underclocking the fsb enough. I.E making a amd 2500+ post as a 2800+ or vice versa

----------


## BrianHawley

You can check anything you like (or not). 

I agree clock speed is not the best choice!

Yes DotNet code can be pulled apart - but that's an argument against writing in DotNet at all - not against this methodology. You  could use the same methodology in C++ for example.

The idea of having unique log-ons to a server is interesting, but what happens if the server is down? What about non-Internet apps or people with dial-up? What happens when you sell a hundred billion copies and overload your server? 

There is no perfect method. They all have a down-side. It's an individual choice.

----------


## pixolut

Consider the following instead of using the Pack method (I had converted this whole thing to VB.Net, so the example is in VB.Net here):


VB Code:
Public Function Value() As String
  Dim output as String = CPUID() & BIOSID() & DISKID() & BASEID() & VIDEOID() & MACID()
  Dim SHA As New SHA1Managed
  Dim tmpOutput as String
  tmpOutput = Convert.ToBase64String(SHA.ComputeHash(ASCIIEncoding.ASCII.GetBytes(output)))
  Return HttpUtility.UrlEncode(tmpOutput).Replace("%", "")
End Function

Also, you must add this to your imports:


VB Code:
Imports System.Text
Imports System.Security.Cryptography
Imports System.Web

Rationale: 

The SHA1 hash algorithm will provide a strong unique key for the 'finger print' whereas the pack algorithm takes strongly unique data and dilutes it by packing it in to 8 digits! The extra encoding (Base64) and conversions (HTTP URL) do not reduce the strength of the hash - they only serve to simplify transmission by making it URLEncoded without any escape characters it becomes a 'plaintext' key which can be transmitted over an HTTP URL query or stored in a database without any string checks or conversions required.

Finally, the SHA1Managed is used as it is a 100% managed implementation of encryption engine. This code should work on Windows 98 as well as other .Net reference platforms without banging native security APIs(hence 'managed').

----------


## BrianHawley

Yes, ok.

Maybe I'm missing something, but why would you want to encrypt the hardware fingerprint? It's not as if it is something secret. The pack routine was just to reduce the size. 

As for loss of discrimination, 8 digits gives 100 million possible fingerprints. We have been using this for a while and have over 800 fingerprints registered - none the same so far. Of course it can happen, but this is not intended to be military grade security. In DotNet they can always pull the code apart anyway if they want to break in!

----------


## pixolut

Brian, I have not encrypted the fingerprint at all - I have created a hash. The purpose of a hash key is to provide some level of uniqueness algorithm between keys. For example, a 1 bit hash would have a 50% chance of collission - right?

The strength of the hash determines its uniqueness. Thus, an 8 digit ascii packer provides a 'weak' hash (many ways to get the same numeric result)  whilst SHA512 would provide a insanely 'strong' hash; that is the possibility of a conflict is increadibly remote.

Therefore, using SHA1 you get pretty solid level of uniqueness in the fingerprint value... this is important if you are talking self generating software serial numbers through internet auto activation or some such mechanism.

More information on SHA1:
http://www.answers.com/topic/sha-family

Regards
Joe

----------


## BrianHawley

Yes, you are right of course.

And I should have read your post properly before jumping to conclusions!!

Sorry about that.  :Blush:

----------


## pixolut

no worries mate.

----------


## dest2912

Hello,
Can some please help me. I am trying to get a key for my hardware fingerprint. I am really new to this stuff, can you please show me how to do that?

----------


## BrianHawley

If you just cut and paste the code in the post at the top of this thread, it should work for you. You can then step through the code and see how it works.

----------


## mendhak

This method works only in XP and 2003:



```


    class HardDrive
    {
        private string model = null;
        private string type = null;
        private string serialNo = null; 
        public string Model
        {
            get {return model;}
            set {model = value;}
        } 
        public string Type
        {
            get {return type;}
            set {type = value;}
        } 
        public string SerialNo
        {
            get {return serialNo;}
            set {serialNo = value;}
        }
    } 



```



```



        ArrayList hdCollection = new ArrayList();
            ManagementObjectSearcher searcher = new
                ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");

            foreach(ManagementObject wmi_HD in searcher.Get())
            {
                HardDrive hd = new HardDrive();
                hd.Model = wmi_HD["Model"].ToString();
                hd.Type  = wmi_HD["InterfaceType"].ToString(); 
                hdCollection.Add(hd);
            }
            searcher = new
                ManagementObjectSearcher("SELECT * FROM Win32_PhysicalMedia");
   
            int i = 0;
            foreach(ManagementObject wmi_HD in searcher.Get())
            {
                // get the hard drive from collection
                // using index
                HardDrive hd = (HardDrive)hdCollection[i];

                // get the hardware serial no.
                if (wmi_HD["SerialNumber"] == null)
                    hd.SerialNo = "None";
                else
                    hd.SerialNo = wmi_HD["SerialNumber"].ToString();
    
                ++i;
            }
            // Display available hard drives
            foreach(HardDrive hd in hdCollection)
            {
                Console.WriteLine("Model\t\t: " + hd.Model);
                Console.WriteLine("Type\t\t: " + hd.Type);
                Console.WriteLine("Serial No.\t: " + hd.SerialNo);
                Console.WriteLine();
            } 



```

----------


## BrianHawley

> This method works only in XP and 2003:


Is anybody running any other version of Windows these days ?   :Smilie:

----------


## azountas

Hi all,

Does anybody know if the WMI code shown at the top of thread works under specific security (or other) circumstances?
I have been using BIOSSN and MACADDRESS for my software, and it seems it works OK on some PCs, while not working at all on others.

Thanks

----------


## BrianHawley

MAC Address would of course depend on having a network card (or equivalent) present and is perhaps not the best choice as network cards tend to be changed. There is also the possibility of changing the MAC address with a utility to whatever the users wants, when it is in firmware.

Bios serial should be okay as far as I know. Maybe some generic BIOS chip sets with no number, but I guess they should return something, if only a row of zeros or a null.

Can you define "not working". Does it cause a hang, or you just don't get back a number you can use?

If you restrict your software to XP and above (i.e. check the OS), I THINK that can only run on PCs with a modern CPU - i.e. with a serial number. Anybody know more about that?

----------


## pixolut

Funny thing, after going to all the trouble of updating this software to generate strong hash keys etc. I had to finally abandon the entire approach in my application. We had deployed the application to a broad consumer base with a crossection of different platforms. 

   We were never able to get WMI to work correctly under Windows98 - even with the Windows 9x WMI update from Microsoft:
http://www.microsoft.com/downloads/d...displaylang=en

 Thats not too bad - and we were willing to use a fallback manual registration scheme for those clients and use the WMI approach for Windows 2000, XP and 2003. The problem we found however, was that lots of security apps lock down aspects of WMI. We have never been able to establish where exactly the lockdown occurs - but it seems to be in the system policies. (thats an educated guess, so if you figure the real place let me know)... so even in latter versions of the OS - the ability to rely on WMI in a customer installation was not there.

 :Confused: 

 Some (edited) error reports follow; you will notice they reresent the two key issues WMI message filter and the non existance of WMI component registration:




> SYS: Microsoft Windows NT 5.1.2600.0
>     CLR: 1.1.4322.2032
>    System.Runtime.InteropServices.COMException (0x80010002): Call was canceled by the message filter.
>        at System.Management.ManagementScope.Initialize()
>        at System.Management.ManagementObject.Initialize(Boolean getObject)
>        at System.Management.ManagementClass.GetInstances(EnumerationOptions options)
>        at System.Management.ManagementClass.GetInstances()
>       at pixolut.itoknet.CommonObjects.Hardware.get_CPUID() in C:\itok\iTOKNET\Common\CommonObjects\Security\Hardware.vb:line 67
>       at pixolut.itoknet.CustomerClient.Main.TestCompatability() in C:\itok\iTOKNET\Client\CustomerClient\Main.vb:line 417


 


> SYS: Microsoft Windows NT 5.1.2600.0
>   CLR: 1.1.4322.573
>   System Info: Microsoft Windows NT 5.1.2600.0
>   System.Runtime.InteropServices.COMException (0x80040154): Class not
>   registered   at System.Management.ManagementScope.Initialize()
>      at System.Management.ManagementObject.Initialize(Boolean getObject)
>      at System.Management.ManagementClass.GetInstances(EnumerationOptions options)
>      at System.Management.ManagementClass.GetInstances()
>  at pixolut.itoknet.CommonObjects.Hardware.identifier(String wmiClass,String wmiProperty) in C:\itok\iTOKNET\Common\CommonObjects\Security\Hardware.vb:line 98
> ...


  I finally ended up going with a different approach; 

 1. We take a sample of certain registry keys in HKLM\Software\Microsoft\Windows\CurrentVersion and HKLM\System\CurrentControlSet\Enum to create a similar kind of 'fingerprint' 

 2. Then we write a random key ( using [GIUD].newGuid().toString() ) to a place in the registry. This acts as a 'salt' for our hardware profile: it is *technically possible* for the same registry values from hardware and os details in the registry - so we salt it using a very random key.

 3. To perform our fingerprint we read the registry 'fingerprint' values and simply append them to a string. The guid registrykey is read and appended too. Then make a single hash key from it (using the SHA1 example in this thread). This acts like a pseudo hardware lock akin to the original finger print approach above but with less chance for failure which WMI introduces. 

  Using the above technique, the license key can not be hacked from the registry - even if you copy the GUID. 

 It CAN be recreated by knowing what the registry keys are which we use to compose it are, however since it comes from the \system\...\CurrentControlSet it uses a dynamic portion of the registry hive and therefore would not really be a viable crack. Of course, if you were that good you could just disassemble and remove jsr's to the fingerprint method in the first place!

  Hope this sheds some light on the problems.

----------


## BrianHawley

That's very interesting. Not something we have come across at all - but you've got me worried. I would have a go at cracking it, but I can't reproduce it! Very strange. Presume you have tried the 'bare' code outside oyur application in case something in the app is conflicting?

Personally I would generally steer clear of the registry for any security related matters. There are lots of utilities that will "photograph" the registry and give you the changes before and after installing software, so the keys at least are easily known.

Well, there's always dongles.....

----------


## azountas

The approach is the following:
1. Display a reg form into which the user enters company name and product code.
2. Calculate an MD5 using the BIOSSN, MACADDRESS. Send the MD5 and the info of step 1 to me.
3. MD5 the above and send them back as an unlock key.

When the user launches the reg form, sometimes BIOSSN is empty and/or MACADDRESS is empty, or both are returned.

The MD5 algorithm works with either empty or not BIOSSN, MACADDRESS (but returns the same when BIOSSN, MACADDRESS are empty). I chose these two because the software is targeted to corporate users (they all have NICs) and I use BIOSSN not CPUID or something else (modern).

My software runs well on both 2000Pro and XPPro.
I cannot tell my customers who run on 2k to upgrade to XP (their IT will freak out   :Mad:  )

I don't like the registry HKLM solution, because I have seen problems on accessing HKLM keys when users do not have local admin rights.

I am thinking of writing a more "low level" routine to get those values.

Any ideas?

----------


## BrianHawley

Hmm... well you got me stumped. All I can say is that the original code I posted has worked perfectly for me every time. There may be circumstances in which it does not, but I have not come across them. I might have another look at it when I get time.

----------


## ricka0

All I can say is bloody  *brillant *   . I googed a few of security terms (looking for code like this) - and google brought me right here. Copy/paste in VS.Net - compile errors, then add System.Management.dll ref - compiles and WORKS!

Good explaination of software protection trade off's Brian.  :Thumb:  
I used a CAD program that required a dongle, the dongle died and I couldn't work. Took it apart, left a note saying *F* U*  in it - I hate dongles.

You can easily lock down much of the registry and WMI in Win03 - which would return those errors. You can get most of these values in native Win32 but then you'd have to step outside of .Net (but it would solve the protection problem).

I have code getting most of these ( Win32/C++/ATL )
The require connection to the network before your app can run is unacceptable to most people. I do my best programming on an airplane.

The SHA hash is the way to go - you get it for free and it makes the code unbreakable. Compressing all the data into an 8 bit hash has the flaw that if some of the data is predicitable(which I'm sure it is), you end up with more like a 4 bit hash. That's how 128 bit encryption was cracked (time has was predictable).

----------


## ricka0

adding a modicum to pixoluts excellent post, here is the C# version


VB Code:
using System.Security.Cryptography;    // SHA.ComputeHash
using System.Web;                     // HttpUtility.UrlEncode
 // add ref to System.Web.dll
 string rSHA(string str){
         ASCIIEncoding AE = new ASCIIEncoding();
        SHA1 shaM  = new SHA1Managed();
        string sTmp = Convert.ToBase64String(shaM.ComputeHash(AE.GetBytes(str)));
        return HttpUtility.UrlEncode(sTmp);
        
    }

Note I change'd it to pass in whatever strings you want. I'll probably use CPU_ID and MAC Addr

I could write a web site that would let you register GUIDS (to enable cust code) - custs could connect to my site and get hash. I could charge say 5 cents for each customer I serviced. Not everybody wants to set up a web site to issue codes.

----------


## BrianHawley

Cool stuff. Glad it worked.

----------


## Kasracer

> The idea of having unique log-ons to a server is interesting, but what happens if the server is down? What about non-Internet apps or people with dial-up? What happens when you sell a hundred billion copies and overload your server?


Redundancy. Dial-up doesn't matter since you're only sending a few bytes (not entire applications).

I also doubt selling a crapload of copies will overload the server unless the server admin doesn't know what he's doing and/or the programmer create a huge amount of overhead.

Like I said, you'd only be transfering a few bytes verifying that someone's login.

----------


## BrianHawley

> Redundancy. Dial-up doesn't matter since you're only sending a few bytes (not entire applications).
> 
> I also doubt selling a crapload of copies will overload the server unless the server admin doesn't know what he's doing and/or the programmer create a huge amount of overhead.
> 
> Like I said, you'd only be transfering a few bytes verifying that someone's login.


Hmm...

Well if I had bought, for instance, a word processor and it had to establish a dial-up connection and swap even a few bytes each time I wanted to use it, I think I would not be a happy customer. Particularly if I was using my lap-top on an airplane. But it's a free world. We can all protect our apps (or not) as we think best.

----------


## pixolut

Veeery good point Brian. Customers have very set expectations when it comes to app protection. High-end and specialist apps are EXPECTED to have a dongle or 'call to base' and be hard to install. A lot of customers actually LIKE this with their high end software as it validates for them the high cost of the software - good examples are high end 3D software packages like Houdini or management software like ShortCuts. Brian's point is valid - because a low end software package is not expected to have high-end security features to protect it. Its all relative and very much built in to the psychology of software consumers.

In the software we built using the authentication scheme above we were using it to enable web based services which were 'pay per use' so customers were very happy to have software which 'chatted' on the internet to validate it was authorized. 

Note - they don't like getting feedback about the chats. We found that some of the errors which were raised that were network related were best ignored. We started out showing them to customers (like 'could not connect to server') but found later that since the software was not being used, the error was useless. Customers get the transport errors when they try to use the software - even if they have been going on all day before hand - thats the first they find out about it. Its all in the psychology...

-Joe

----------


## BrianHawley

Well put.

Use the appropriate tool for the job.

Don't use your Ferrari to transport 20 bags of cement and don't race your 18-wheeler (although there are some people who will do both!)

----------


## nemaroller

I move that we go back to putting programs on cassette tapes - or better yet, punch cards - yes punch cards - that would stop piracy.

----------


## BrianHawley

I've got an old Commodore PET (1970s PC) with programs on tape. Still works! A whole 8 Kilobytes of RAM.

----------


## dglienna

paper tape was the bomb!  300 baud also!

----------


## dest2912

> If you just cut and paste the code in the post at the top of this thread, it should work for you. You can then step through the code and see how it works.


Hello,
can you please help me do this. So I put this code in the program(forgot the name) as C#, now what, how do I make into a exe file?


using System;
using System.Globalization;
using System.Threading;
using System.Resources;



using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Reflection;
using System.Text;


namespace Whatever
{

	public class Fingerprint
		//Fingerprints the hardware
	{
		public string Value()
		{
			return pack(cpuId()
				 + biosId()
				 + diskId()
				 + baseId()
				 + videoId()
				 + macId());
		}
		private string identifier(string wmiClass, string wmiProperty, string wmiMustBeTrue)
			//Return a hardware identifier
		{
			string result="";
			System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
			System.Management.ManagementObjectCollection moc = mc.GetInstances();
			foreach(System.Management.ManagementObject mo in moc)
			{
				if(mo[wmiMustBeTrue].ToString()=="True")
				{

					//Only get the first one
					if (result=="")
					{
						try
						{
							result = mo[wmiProperty].ToString();
							break;
						}
						catch
						{
						}
					}

				}
			}
			return result;
		}
		private string identifier(string wmiClass, string wmiProperty)
			//Return a hardware identifier
		{
			string result="";
			System.Management.ManagementClass mc = new System.Management.ManagementClass(wmiClass);
			System.Management.ManagementObjectCollection moc = mc.GetInstances();
			foreach(System.Management.ManagementObject mo in moc)
			{

				//Only get the first one
				if (result=="")
				{
					try
					{
						result = mo[wmiProperty].ToString();
						break;
					}
					catch
					{
					}
				}

			}
			return result;
		}

		private string cpuId()
		{
			//Uses first CPU identifier available in order of preference
			//Don't get all identifiers, as very time consuming
			string retVal = identifier("Win32_Processor","UniqueId");
			if (retVal=="") //If no UniqueID, use ProcessorID
			{
				retVal = identifier("Win32_Processor","ProcessorId");

				if (retVal=="") //If no ProcessorId, use Name
				{
					retVal = identifier("Win32_Processor","Name");


					if (retVal=="") //If no Name, use Manufacturer
					{
						retVal = identifier("Win32_Processor","Manufacturer");
					}

					//Add clock speed for extra security
					retVal +=identifier("Win32_Processor","MaxClockSpeed");
				}
			}

			return retVal;

		}
		private string biosId()
			//BIOS Identifier
		{
			return identifier("Win32_BIOS","Manufacturer")
			+ identifier("Win32_BIOS","SMBIOSBIOSVersion")
			+ identifier("Win32_BIOS","IdentificationCode")
			+ identifier("Win32_BIOS","SerialNumber")
			+ identifier("Win32_BIOS","ReleaseDate")
			+ identifier("Win32_BIOS","Version");
		}		
		private string diskId()
			//Main physical hard drive ID
		{
			return identifier("Win32_DiskDrive","Model")
			+ identifier("Win32_DiskDrive","Manufacturer")
			+ identifier("Win32_DiskDrive","Signature")
			+ identifier("Win32_DiskDrive","TotalHeads");
		}		
		private string baseId()
			//Motherboard ID
		{
			return identifier("Win32_BaseBoard","Model")
			+ identifier("Win32_BaseBoard","Manufacturer")
			+ identifier("Win32_BaseBoard","Name")
			+ identifier("Win32_BaseBoard","SerialNumber");
		}		
		private string videoId()
			//Primary video controller ID
		{
			return identifier("Win32_VideoController","DriverVersion")
			+ identifier("Win32_VideoController","Name");
		}
		private string macId()
			//First enabled network card ID
		{
			return identifier("Win32_NetworkAdapterConfiguration","MACAddress", "IPEnabled");
		}
		private string pack(string text)
			//Packs the string to 8 digits
		{
			string retVal;
			int x = 0;
			int y = 0;
			foreach(char n in text)
			{ 
				y ++;
				x += (n*y);
			}

			retVal = x.ToString() + "00000000";

			return retVal.Substring(0,8);
		}

	}
}

----------


## BrianHawley

I would respectfully suggest that before you start with code of this complexity, you learn the fundamentals of how to use Visual Studio .NET and how to generate EXE programs. There is a lot of help documentation included with Visiual Studio itself, as well as a lot of free information on the  Internet. If you run into specific problems, then I'm sure if you post them in the appropriate places on this fiorum, people wil be glad to help.

----------


## Malf

> 5) Keep security code isolated and encapsulated in different places - don't just call the same routine from different places. Checksum the EXE using a non-standard method. Have several layers of security that cut in at different and increasing time intervals - hacker breaks the daily one and sells your code, then the weekly one cuts in - he breaks that then the monthly one cuts in etc. etc.


Hi
Can you elaborate on this?  What do you mean "security code isolated and encapsulated in different places"?  Like have a real code class then a dozen 'empty' fn/cls that just forward to the real one?

Checksum - difficult in the .net obfuscation world

"Have several layers of security..." that would require more than once code right?  I don't see how you can have diff checks for one reg code.

----------


## BrianHawley

> Hi
> Can you elaborate on this?  What do you mean "security code isolated and encapsulated in different places"?  Like have a real code class then a dozen 'empty' fn/cls that just forward to the real one?
> 
> Checksum - difficult in the .net obfuscation world
> 
> "Have several layers of security..." that would require more than once code right?  I don't see how you can have diff checks for one reg code.


Most hackers will break your protection by finding the block of code in your application that does the security, then modifying it or commenting it out. 

If you have two (or more) completely separate code blocks that each independently do security, then the hacker might find one (and remove it) but not the other(s). 

What I mean by layers, is that if one block checks security every time the program starts, and stops the program if it is pirated, then the hacker might find this block and remove it. But if there is a completely separate second block that only checks security, say every 9th hour, then he/she may not at first notice this and may release the program as 'hacked'. 

But anyone who uses it will find it stops working after nine hours (preferably with a storm of false warnings about it being a hacked/infected version, to frighten-off any illegal user) . The hacker becomes aware of this and hacks the second block - but there is a third block that impliments security only on Friday 13th - and so on for as far as you want to go. You can keep a hacker tied up for a long time until he loses interest and his "client base" loses faith in the hacked version.

Not a war you can win against someone persistant and skilled, but there is no need to make it easy for them.

We have a message that pops up in hacked versions of our software that tell the user we will give them a free copy if they help us find and succesfully prosecute the person who gave them the illegal version. We have nailed 2 people so far.

----------


## jonrmorgan

Hi Brian,

Really interesting, and long running !, thread.

I'm trying to implement a "low-end" license key and at first sight your fingerprint (with the SHA1 hash mod.) seemed ideal. However when I did some testing I found the return values were regularly changing and in particular I found, surprisingly, that Win32_Processor can change even when there has been no hardware change. 

Have you or anyone else come across this problem ?

The answer may lie in the value WMI returns for Win32_Processor. For "convenience" here's what the MSDN docs less than helpfully say on the subject:

" Processor information that describes the processor features. 
For an x86 class CPU, the field format depends on the processor support of the CPUID instruction. If the instruction is supported, the property contains 2 (two) DWORD formatted values.The first is an offset of 08h-0Bh, which is the EAX value that a CPUID instruction returns with input EAX set to 1. The second is an offset of 0Ch-0Fh, which is the EDX value that the instruction returns. 
Only the first two bytes of the property are significant and contain the contents of the DX register at CPU resetall others are set to 0 (zero), and the contents are in DWORD format. "

So that's perfectly clear, eh ! 

It's a while since I've hacked at assembler but I thinks it saying that what's returned depends on the status of the EAX register. This may account for the lack of consistency.

Appreciate any inspiration - on reflection maybe it'd be better to drop processor id altogether as I've also noticed from my download logs that some machines just don't return any value (maybe this is the security policy issue pixolit came up against). And of course the above doc only applies to x86 CPUs. 

I'd appreciate views on what's the best fingerprint combination - your original concept was "the more the better" but given the above problems, I'd like to balance comprehensiveness with reliability. 

There's also the question of giving weightings to fingerprint items and scoring the overall value against a threshold failure point - I believe this is how MS  others do it.

Hope this keeps the thread going. In fact come to think of it this subject is worth a dedicated site !


Jon

http//www.fm-alive.com

----------


## jezbo

I've used this mechanism in my program licencing and it works pretty well. 

I was worried that the fingerprint would change on every configuration change, so I only combine macID, motherboardID (only the serial#) and biosID (only the id code and serial#). I only resort to cpuID, diskID, or videoID if the default IDs return nothing (possible I suppose).

Only one of my 20 users has needed a new key so far (in 2 active months) because of a fingerprint change; I haven't found out exactly why yet though. If it becomes more frequent I'll have to look into building the fingerprint from something that changes less often - perhaps just the motherboard serial number only. 

The more things you make it dependent on, the more unique and secure it may be, but the more likely it is to change frequently I suppose - it's a trade off.

But, does 
    identifier("Win32_BaseBoard", "SerialNumber") 
always return a value ?

----------


## batgurl

A certain proportion of users will install a second copy on another machine and ask for a new code. That's just one of the drawbacks of this method, You have to use judgement if a user requests several codes in a short time and decide if they are honest or not. But at least you can limit each dishonest user to half-a-dozen pirate copies (or whatever you decide). They do not have the capacity to  give away thousands.

----------


## ..:RUDI:..

> A certain proportion of users will install a second copy on another machine and ask for a new code. That's just one of the drawbacks of this method, You have to use judgement if a user requests several codes in a short time and decide if they are honest or not. But at least you can limit each dishonest user to half-a-dozen pirate copies (or whatever you decide). They do not have the capacity to  give away thousands.


Hmm, I remember purchasing a game where it used something like this hardware validation method, and it only let your request a key 3 times per 2 months, that seems to be an effective method.   :wave:  

I mean seriously, how many people change their computer configuration over once each month?   :Ehh:

----------


## batgurl

That's a good idea. Limiting installs by time sounds fair.

You could also perhaps do something with upgrades. Only allow upgrades to the system with the most recently issued activation key.

If the program has an automatic 'check for updates over internet' facility, you could even build in automatic deactivation (or revert to demo version) - although that's a bit risky. You would need to be very sure your code was robust.

----------


## BramVandenbon

> Yes, ok.
> In DotNet they can always pull the code apart anyway if they want to break in!


That's not totally true. In that matter .NET can be just as secure as C++. All you need to do is precompile it to native code, after compilation. You can do this with Ngen.exe (The Native Image Generator).

In fact not only is it good for security, but also for the speed. People who claim that C# is too slow should just use this little program because it makes C# code as fast as C++.

By the way, there are a lot of people who always compile their code in Debug mode, and who forget to compile in Release mode when releasing software. This also makes a big difference in speed and in security too I guess.

----------


## xheo

Unfortunately that's not true. NGEN still requires the original meta data to be available which makes it easy to read and disassemble. And if the NGEN'd assembly is not valid for the given machine - say compiled on x86 machine and run on x64 - then the original assembly is used instead. There are other conditions where the original file is used in place of the NGEN assembly but I don't recall off hand. The only thing NGEN is good for is a potential increase in _load time_. It has 0 benefit to run time since the code is JITTed exactly once by the runtime and runs with the same performance characteristics as an NGEN assembly. While Microsoft warns that NGEN assembly use might prevent the JIT from taking advantage of advanced scheduling and instruction ordering for the specific machine, in practice the NGEN code and JIT code are generally the same.

The only way to protect your assembly from prying eyes and modification is to use an assembly encryption system.

 [advertisements snipped]

----------


## hnazee01

Nice code. That's what I was looking for. I do have one suggestion though; consider removing the macId() since I ran into a problem. I have a wireless card and a Lan card in my laptop and some days I use either. The Fingerprint I received was different because of this and my code did now work.

Thanks
Nick

----------


## rsalgado

This is a very nice code.

I am somewhat new to this, so sorry if the question is a bit basic.

If I put the check on the fingerprint as follows.  The idea is: if the person tried to run the program, the person would get a windows that would allow him or her to generate the fingerprint.

if (fp.Value() == "some number")
            {
                Application.Run(new frmSTABL());
            }
            else
            {
                Application.Run(new frmActivationMsg());
            } 

then the user will have to reinstall software he/she may have already downloaded and installed.  Is there a better way?

Thanks,

RS

----------


## rsalgado

After thinking about this, it seems that the idea is to use the encryption functions of .NET so that all I have to send the user is a file with the encrypted computer fingerPrint.  If I am not correct, I hpe someone will point me in the right direction.

Thanks.

----------


## chetachukwu

Hello, My Name Is Mr. Chetachukwu I Will Really Like To Know Why Your Site Is Not Showing Me Where I Will Send Sms Message Please Advis Me Urgently

----------


## RudiVisser

> Hello, My Name Is Mr. Chetachukwu I Will Really Like To Know Why Your Site Is Not Showing Me Where I Will Send Sms Message Please Advis Me Urgently


What?

And thanks again for this code, it's great. As I said before (as ..:RUDI:..), very effective.

----------


## shakir1311

well guys the code work well but there is a bit of problem. The code can not 
enumerate devices that are disabled by the device manager.

e.g run the program and generate your serial number ( unique to your system).
save the number and then disable your network card( or enable it if it is already disabled). run the program again and u will get a different number.

----------


## andyd273

First off, I've been looking for something like this, and it is awesome.
And props for the VB.net translation.

Second, just in case someone else runs into the problem I did, you have to add in the System.Management.dll as a reference, because this part threw me for a couple minutes (I'm a bit of a newbie with .Net)

Once again, awesome job.

Oh, one more thing... the program I am working on has to connect to an online database on one of our servers to get the data (we don't want to give the users the data, since that is what they will be paying us for). so since they will be accessing a sql server anyway, I plan on just storing their registration key and fingerprint in the database, and make it so they have to match or no data for them.
That way we can charge a subscription fee, and if they cancel their subscription, we can invalidate their key and cut them off. They will still have the program, but it won't do them any good.

Just my thoughts

----------


## andyd273

> well guys the code work well but there is a bit of problem. The code can not 
> enumerate devices that are disabled by the device manager.
> 
> e.g run the program and generate your serial number ( unique to your system).
> save the number and then disable your network card( or enable it if it is already disabled). run the program again and u will get a different number.


Just remove the + macId() from the value function.
The other stuff can't/shouldn't be disabled.
cpu, bios and base aren't disableable (I don't think),
and if you disable the disk or the video then other bad things happen (like not being able to run your computer). Not impossible, just unlikely.

Then as a last safeguard, I liked the one idea of letting them update their system id 3 times over 2 months. That way if they do have to disable something, it won't bother them. but if they are sharing a key, it'll lock them out pretty quick.

----------


## rickbear

Hey...

I am not sure if this thread is still allive, but I found it on a google search and found it interesting.

I have a question though:

You can make your application to start only once on each machine to prevent multiple instances of your application running if you lets say had an application which had a license allowing you to have 1000 database entries of some kind.
If more instances of the application were allowed, then all this hardware checking would make no sense because hardware information would be the same for each instance.

But:
What if one's application is installed on a virtual machine - you then clone the virtual machine and run two instances of the virtual machine on the same physical machine - each now allowing 1000 database entries = 2000 entries. Is there some way to distinguish the two virtual machines? I've tried to look at some of the wmi calls and the info looks similar on both virtual machines. But perhaps I haven't looked deep enough?

When I run clones of one virtual machine on two different physical machines there are differences though. But it is the instance above I am worried about.

- rick -

----------


## andyd273

> What if one's application is installed on a virtual machine - you then clone the virtual machine and run two instances of the virtual machine on the same physical machine - each now allowing 1000 database entries = 2000 entries. Is there some way to distinguish the two virtual machines? I've tried to look at some of the wmi calls and the info looks similar on both virtual machines. But perhaps I haven't looked deep enough?


Well, it seems like a possible solution to this would be to generate some kind of key on the first run of the program, MD5 the datetime or something. Then just allow 1000 entries per key. So if they clone the machine it'll have the same key and the same entries. If they somehow put the key into a different machine then the fingerprint will be different, and if they run it on the same machine then who cares, because they only get 1000 entries anyway, and only one person can effectively use it at the same time...
It seems like a key/fingerprint combo could solve this problem.

----------


## Tobiasgar

I have developed checking computer finger print program in c#. Now what logic i should apply to check finger print before installing setup of application.

Pls help. :Eek Boom:

----------

