Saturday, September 28, 2019

Memorable Computer Generated Keys

Computer systems often publish information for humans to read: invoices, sales dockets, memberships, subscriptions, travel bookings, etc. Items like these are expected to have some sort of unique 'key' associated with them, so for example, if you phone an airline to change a flight they will ask "what is your booking number?", then your correct reply allows immediate verification and you can proceed.

So what sort of 'key' should be used for documents that will be consumed by humans? Here are some choices I can think of:

Database Identity Key

Many relational databases (and others) can generate incrementing unique numbers like 0003145 as primary keys. This is good because numbers (of reasonable size) are simple for people to understand. It's bad for security though, because these numbers are predictable and Mallory can guess lots of numbers and try to hack or forge his way into private information.

'Jump' Numbers

Instead of predictably incrementing keys, how about ones that 'jump' with a random gap to the next one? This will produce numbers that are easy to read, and Mallory would have to try many keys to possibly find a valid one to attack. The size of the random jump has to be chosen as a tradeoff between the growth of the key and security through unpredictability.

Guids

Many databases generate a GUID (aka UUID) for each record. This is great for programmers who can be sure these unique keys will never clash with another one in the world. They are create-and-forget ids that are secure because they are sparsely distributed in 122-bit space and it's infeasible to guess them. The bad news is that a value like 8f50806b-1f88-4cd7-9112-4512045df69b on an airline reservation will boggle the mortal mind.

You could publish just a part of it as a key, like the 8f50806b prefix for example. This is not a really friendly string for a person to read, but it may be acceptable (making it uppercase might help the eye). You just have to be sure that there's enough entropy in the substring to guarantee uniqueness and unpredictability, and with GUIDs this is probably the case for normal use. See Birthday Attack for more information.

Artificial Keys

Another technique I quite like is to create an artificial random key that looks friendly to the eye. A key like KLB977 looks like a Victorian car number plate, or 125-982-763 is easy to recognise and dictate. Just pick a consistent format that seems friendly, perhaps something that's familiar to your local culture or language.

You have to be sure that the probability of generating a duplicate artificial key is small enough. If the key is short then you would probably generate the key and do a quick database lookup check that it's not a duplicate before using it. In the previous examples there would be 17.5 million 'number plate' keys and 1 billion 9-digit keys.

Fake keys composed of numbers and letters need some extra care to ensure they are human-friendly. Some numbers and letters have similar shapes and should be avoided. When I make fake keys I compose them out of the following character pool (notice some are missing):

123456789ABCDEFGHJKLMNPQRSTUVWXYZ

If you're extra conservative you may consider the characters JUVW a bit troublesome as well and remove them. Different fonts may affect your choice of 'bad' characters. With the reduced character set, the number of 'number plate' keys reduces to 10 million and the 9-digit ones reduce to about 0.4 billion, but this might be quite acceptable for moderate usage scenarios.

Addendum: Check Digits

In cases where generated keys are under your control, as in the cases of the 'Jump' Numbers and Artificial keys discussed above, you may consider using a check digit scheme to allow basic error detection for your keys.

The Wikipedia article lists many schemes, and there many other specialised ones like ISO 6346 which is used to identify shipping containers. You might even like to make up your own, perhaps checking that each numeric key is within ±3 of a prime number (I made that up). The Verhoeff check digit algorithm is technically very interesting and effective.

Friday, September 20, 2019

Installer Customer Information Dialog

When creating an MSI installer using a Visual Studio Setup Project you may want the user to enter their company name and a serial number as some sort of anti-piracy measure. Installers for many commercial products use this technique and it would nice if a similar step could be inserted in the wizard sequence in a Setup Project with minimal effort.

In the User Interface Editor you can add a Customer Information dialog which prompts for a Name, Organization and Serial number. It looks like this sample:



Note that the second field 'Organization' is optional and the default is to hide it. Toggle the visibility using the ShowOrganization property in the dialog's properties.

For many years I assumed that it was impossible to retrieve and validate the values from this dialog without writing a C/C++ custom action and manually registering it in the MSI tables. There are web articles that discuss that technique, but it was too much bother for me and I just assumed the dialog was too hard to use and I ignored it.

However, this week I found an article by accident that hinted that the Serial number could be retrieved using the PIDKEY property name. This led me to a Property Reference page where some interesting User Information properties are listed at the bottom of the page. After some experiments I found that the property names USERNAME, COMPANYNAME and PIDKEY correspond to the three fields in the dialog.

Now the challenge was to retrieve the Customer Information dialog values and validate them. I also wanted to save the values after successful install in some well-known location so they can be used by the product later at runtime (this was a personal requirement).

Some Bad News

Custom action (CA) code created by Visual Studio is not of a type that runs in the UI sequence, so it's not possible to interactively validate the serial number. This is where C/C++ code would be required with special registration.

There is still a slightly clumsy way to validate the serial number once the UI sequence ends and the install sequence starts and the managed code custom actions run. If the managed CA detects a bad serial number then it can throw an Exception to cause a message box to display something helpful, then the install rolls back and is cancelled. It's a bit of a nuisance that you can unwittingly enter a bad serial, click through a few more wizard steps, start the install and then discover the Serial is wrong, but it's probably an acceptable inconvenience.

The Good News

In summary, here is how to retrieve and validate the Customer Information dialog values.

• Write a CA class derived from the Installer class (contents discussed later).

• In the Custom Actions editor, add a CA to each of the four install steps which points to the project output containing the CA class (probably your main application project).

• Set the CustomActionData for each of the CA nodes to this (wrapped for easy reading):

target="[TARGETDIR]\"
/pidkey="[PIDKEY]"
/companyname="[COMPANYNAME]"
/username="[USERNAME]"

This is the 'trick' that lets the dialog values be passed down into the CA when it runs. If you have other custom dialogs in the UI sequence, add their properties to the list.

• In the CA's Install override method do something like this skeleton:

public override void Install(IDictionary stateSaver)
{
  base.Install(stateSaver);
  string serial = Context.Parameters["PIDKEY"];
  if (serial != "314159") throw new Exception("Bad serial number");
  // You could save the parameters now
}

Enhance this raw code to be crash-resistant, then use whatever serial validation check suits your needs. The thrown message will appear in a popup and cause the install to cancel and roll back.

If the validation passes, then you could loop through the Context.Parameters collection and write the key-value pairs to a file in a well-known location. The product could read the values at runtime and, for example, it could re-validate the serial as an extra anti-piracy measure.

Serial Validation

The logic that validates the serial number can be a simple or complex as you want. A simple scenario would be to check that the serial number matches the hash of some local environmental information such as the NetBIOS machine name or the Windows Product ID (displayed in Control Panel System). This would restrict installation to specific computers or specific copies of Windows.

Remember though that if the hash algorithm code is inside the installer then someone can easily extract it and reverse engineer it.

A more complex scenario may involve "phoning home" to a web service where sophisticated licensing rules could be implemented.

Summary

So after a decade of ignoring the Custom Information dialog because I thought it was too tricky, a tiny hint led me to discover that there is not really any trick at all to using it. The 'trick' was discovering the special property names USERNAME, COMPANYNAME and PIDKEY.

Note that the first two text fields in the Customer Information dialog seem to be prefilled with the name of the current Windows user account and the Windows registered customer name respectively. If you blank the fields out and continue, the same default values seem to be used anyway.