Search code examples
password-protectioncopy-protection

Implementation of software serial numbers


Let's say I have written a program and want to distribute it to people. I want to be able to request a serial number from the user during installation which will validate that they have a licensed copy of the software. Additionally, I want the serial number to store which edition of the software they have access to, and when the license expires.

There are at least two conceptual ways I can think of to accomplish this:

  1. Generate a serial on the server where user buys software, and email it to them. The software connects to server during installation and activates the product. The server returns the license privileges and expiration. The serial has no specific format/mathematical rules, it is simply checked against the database on the server.

  2. Generate a serial for the customer and email it to them. The serial has some special mathematical properties which allow the program to check if it is valid, what license it corresponds to, and when it expires.

I'm mostly interested in the 2nd method. What techniques are used in encoding this information in a serial number? If you could give a brief overview that would be great. Otherwise, can you recommend some good books or websites which discuss this?

I find it strange that in all the years I've been coding, I've never actually seen an implementation or description of these techniques.


Solution

  • Okay, so two weeks without an answer. I'm going to answer with a very simple method I've come up with for generating relatively secure, and highly scalable serial numbers using elementary methods.

    As a mathematician, I'm quite positive that there are some advanced techniques for storing all sorts of information in a serial number - but I'm mainly interested in quick-and-dirties.

    Here's a naive, non-mathematical, brute-force technique to consider:

    Create a byte[] array containing the characters you want to use. You can use hex only, but there's no reason to limit yourself. Why not use the whole alphanumeric range minus '0'/'O', and '1'/'I' (for obvious reasons).

    Next, write a function as follows (example is C#):

    byte[] genRandomSerial(int length, byte[] characters, Random r)
    {
      var sn = new byte[length];
    
      for (int i = 0; i < length; i++)
        sn[i] = characters[r.Next(0, characters.Length)];
    
      return sn;
    }
    

    This will give you a random serial number, which we don't know is valid or not.

    Next:

    int sum(byte[] sn, MD5 md5)
    {
      val = 0;
    
      foreach (byte b in md5.ComputeHash(sn))
        val += (int)b;
    
      return val;
    }
    

    and then

    bool validate(byte[] sn, uint radix, uint expected, MD5 md5)
    {
      return (sum(sn, md5) % radix == expected);
    }
    

    What we have now is a way of summing together the 16-byte output of the MD5 hashing function, and evaluating whether the sum modulo n is equal to some x.

    Now, decide on how many serial numbers you want to exist. The more serial numbers exist, the easier it is that someone will randomly guess a valid combination.

    Split your random serial into blocks. Let's say 5 blocks of 4, giving 20 characters in the form: ABCD-EFGH-IJKL-MNOP-QRST

    Create 5 arrays out of your serial number:

    {A, B, C, D}, {E, F, G, H}, {I, J, K, L}, {M, N, O, P}, and {Q, R, S, T}.

    test to see if your 5 arrays validate as follows:

    if (validate(block1, radix, expected, md5))
      // This block is valid.
    

    If you set radix to 2, then there is a 1/2 probability that the block will be valid. If you set the radix to 10, then there is a 1/10 probability that the block will be valid. If you have 5 blocks, and you set the radices each to 10, then the probability that the whole serial number will be valid is 0.1^5 = 0.00001. (In other words, 1 in every 100000 random serials will be valid. That means if you use the full alphanumeric range minus '0'/'O', '1'/'I' then you have (8 + 24)^n * 0.00001 = ~1.2 * 10^19 valid keys for a serial length of 20. That's a lot - but remember you're not going to find them all anyway. The higher your radix, the more secure the serial will be, but the longer it will take to generate).

    Note, 'expected' should be somewhere between 0 and radix-1.

    So now we have a way of validating a particular serial number as valid, but how do we store what type of serial it is? As a matter of fact, we already have the method to do it. Taking the entire random (but validated) serial 'sn':

    int licenseType = sum(sn, md5) % 4; // Where 4 is the number of licenses you want to have
    
    if (licenseType == 0)
    {
      // Evaluation
    }
    else if (licenseType == 1)
    {
      // Standard
    }
    else if (licenseType == 2)
    {
      // Full
    }
    else // licenseType == 3
    {
      // Unrestricted
    }
    

    The number of each type of license will gradually level out as you generate more and more keys.

    If you want to store additional information in the key, for example expiry dates, you can use similar methods. You could, for example take the sum of the odd characters modulo 12 to get an expiring month, and the modulo 31 of the sum of the even characters to give the expiring day.

    The more of these restrictions and sub-divisions you apply, the longer it will take to generate each type of key.