Many types of software use Product Keys in order to prevent casual piracy. This is a fairly straightforward method of at least curbing it. It doesn’t put up a giant roadblock, but for corporate environments, working around it is a “smoking gun” for an audit since you don’t accidentally use a keygen. This makes it a fairly attractive option.

Another interesting way of making use of product key as a data storage mechanism, by encoding product capabilities and characteristics into the product key, you can have your program check that the capability is available before allowing the user to use it.

The most straightforward approach is quite simple in concept- you write some data to an encrypted stream, then take the bytes from that stream, base32 encode them stick some dashes on, and bobs your uncle and you’ve got a product key; determining if the key is valid, one merely reverses the process.

This is ripe for making more abstract; different applications could easily have different bits of information that could be encoded, and it should be fairly straightforward to “genericize” it such that the particular bits of key information can be persisted to and from a product key appropriately. We start with the abstract class for defining classes which contain product information to be encoded:

1

2

3

4

5

6

7

8

9

10

publicabstractclassLicenseKeyData

{

protectedLicenseKeyData(Stream SourceData)

{

}

protectedLicenseKeyData()

{

}

publicabstractvoidPersistData(Stream Target);

}

No fancy fencing here; we define an abstract PersistData() method as well as a constructor. Of course, constructors cannot be defined abstract as they aren’t considered part of the interface, but we can enforce that at run-time. This is pretty much a Binary Formatter style interface; it writes to a Stream, and it reads from a Stream, and that’s it. The rest of the work is done elsewhere to encrypt and decrypt the data that was written to that stream into a Product Key. Of course you cannot write many Kilobytes of data to the stream and expect it to encode into a few bytes, so there is definitely a practical limit depending on how long you are willing to make the product keys themselves.

For the encryption, we can use something unique to the system as the password. As it happens, The cryptography registry information includes a unique Machine ID that we can use for this purpose:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

publicstaticStringLocalMachineID

{

get

{

StringReadKey="SOFTWARE\\Microsoft\\Cryptography";

intkeyresult=0;

RegistryKey rk=null;

Stringmkid="";

try

{

rk=Registry.LocalMachine.OpenSubKey(ReadKey);

}

catch

{

}

if(rk!=null)

mkid=(String)rk.GetValue("MachineGuid");

if(String.IsNullOrEmpty(mkid))

{

mkid=System.Environment.MachineName;

}

returnmkid;

}

}

I’ve found that the key isn’t always present. In particular, on some Windows XP systems it can be absent. For compatibility there is an extra consideration for that possibility, which will instead use the Machine Name. That is less unique and can be changed by the user very easily but it’s the next best thing. Also, I’m not trying to come up with the next SecuROM or something, so the added “Crackability” isn’t an issue. Furthermore, we can add a Salt to the encryption; for another piece of unique information, we can use the Windows installation product ID:

This product ID can be converted into a set of bytes by stripping out the dashes, and interpreting it as Hexadecimal. We can use this when constructing the Rfc2898DeriveBytes instance to use for encryption (or decryption) appropriately.

1

2

3

4

5

6

7

8

9

10

11

publicstaticRfc2898DeriveBytes GetPdb(stringpassword)

{

StringsWindowsID=WindowsProductID.Replace("-","");

byte[]useSalt=Enumerable.Range(0,sWindowsID.Length)

.Where(x=>x%2==0)

.Select(x=>Convert.ToByte(sWindowsID.Substring(x,2),16))

.ToArray();

returnnewRfc2898DeriveBytes

(password,

useSalt);

}

This gets used in what is otherwise a straightforward routine for encrypting and decrypting a byte array. The end result is we get fairly straightforward use. First, we define a class that derives from LicenseKeyData, which saves and restores some bits of data to and from the stream via the constructor and the abstract method implementation:

The Handler class uses type constraints to accept only LicenseKeyData derived classes as part of two generic methods to effectively retrieve the data from a product key or encode it into a product key. This makes for relatively straightforward usage:

The guts of the LicenseHandler and implementation of the ToProductCode() and FromProductCode() methods are interesting as well. The ToProductCode<T> definition accepts the derived class instance, and calls the interface method to write to it. Then it converts the data in the MemoryStream to a byte array, encrypts it, base32 encodes it and adds dashes, then returns that as the product Key:

thrownewArgumentException("Type "+typeof(T).Name+" does not have a constructor accepting arguments of type (Stream)");

}

ObjectBuiltInstance=useConstructor.Invoke(newobject[]{Source});

return(T)BuiltInstance;

}

catch(Exception Exx)

{

thrownewInvalidKeyException("Invalid Product Key",Exx);

}

}

This part does the interesting part- It accepts the “standard” stream after we’ve decrypted the information and it’s been tossed into a Memory Stream; we simply find the appropriate constructor and toss the Stream at it and return the resulting instance. We of course throw an appropriate exception if the constructor isn’t found, and if an exception occurs, we will presume that the key is invalid and throw an exception appropriately.

It is, of course, possible to go further. In particular, one could consider the case of a product that is licensed for a specific number of users. One approach for this would be to have a Server program that is given the product key appropriately; the programs on each station will be a stub program with an encrypted block at the end; the stub program connects to the license server and requests the decryption key- if there are fewer than the licensed number of users using the software package, it will increment the count it has internally, and provide the decryption key. The stub program decrypts the data block using the provided key, saves that decrypted data as an executable, and runs it. There would likely be some task in managing the concurrent users, in particular, a user of any product in a suite might be considered only one concurrent user, regardless of the number of programs they are actually running concurrently. But the approach of actually encrypting the data and requiring a key from a license server via a stub program can help prevent users from working around otherwise basic preventions that just skip portions of logic.