Good, so two weeks unanswered. I am going to answer using a very simple method that I came up with to create relatively safe and scalable serial numbers using elementary methods.
As a mathematician, I’m pretty sure that there are several advanced methods for storing all kinds of information in a serial number, but I'm mostly interested in fast and high-quality files.
Here is considered naive, not mathematical, brute force:
Create a byte[] array containing the characters you want to use. You can use only hex, but there is no reason to limit yourself. Why not use the entire alphanumeric range minus '0' / 'O' and '1' / 'I' (for obvious reasons).
Then write the function as follows (example - 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 that we don’t know is valid or not.
Further:
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); }
Now we have a way of summing the 16-byte output of the MD5 hash function and evaluating whether the sum modulo n is equal to some x.
Now determine how many serial numbers you want to use. The more serial numbers there are, the easier it will be to randomly guess a valid combination.
Divide your random serial number into blocks. Let them say 5 blocks of 4, giving 20 characters in the form: ABCD-EFGH-IJKL-MNOP-QRST
Create 5 episodes from the serial number:
{A, B, C, D}, {E, F, G, H}, {I, J, K, L}, {M, N, O, P} and {Q, R, S, T}.
check if your 5 arrays are checked as follows:
if (validate(block1, radix, expected, md5)) // This block is valid.
If you set the radius to 2, then the probability that the block will be valid is 1/2. If you set radix to 10, then the probability of 1/10 will be valid. If you have 5 blocks, and each of them is 10, the probability that the entire sequence number will be valid is 0.1 ^ 5 = 0.00001. (In other words, 1 out of every 100,000 random series will be valid. This means that 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 serial length 20. That's a lot, but remember that you won’t find them all the same. The higher your raxis, the more secure the serial number will be, but the longer it will take for generation).
Note. The expected value should be somewhere between 0 and radix-1.
So, now we have a way to check a specific serial number as valid, but how can we store what type of series? In fact, we already have a way to do this. Accepting all the random (but verified) 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 amount of each type of license is gradually equalized as you generate more and more keys.
If you want to store additional information in a key, such as an expiration date, you can use similar methods. For example, you can take the sum of odd characters modulo 12 to get an expiring month, and modulo 31 the sum of odd characters to give an expiring day.
The more of these restrictions and fakes you apply, the more time it will take to generate each type of key.