using System; using System.Collections; using System.IO; using System.Text; namespace Nuclex.Support.Licensing { /// Typical license key with 5x5 alphanumerical characters /// /// /// This class manages a license key like it is used in Microsoft products. /// Althought it is probably not the exact same technique used by Microsoft, /// the textual representation of the license keys looks identical, /// eg. O809J-RN5TD-IM3CU-4IG1O-O90X9. /// /// /// Available storage space is used efficiently and allows for up to four /// 32 bit integers to be stored within the key, that's enough for a full GUID. /// The four integers can be modified directly, for example to /// store feature lists, checksums or other data within the key. /// /// public class LicenseKey { /// Parses the license key contained in a string /// String containing a license key that is to be parsed /// The license key parsed from provided string /// /// When the provided string is not a license key /// public static LicenseKey Parse(string key) { key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper(); if(key.Length != 25) throw new ArgumentException("This is not a license key"); BitArray bits = new BitArray(128); uint sequence; // Convert the first 4 sequences of 6 chars into 124 bits for(int j = 0; j < 4; j++) { sequence = (uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 + (uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 + (uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 + (uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 + (uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 + (uint)codeTable.IndexOf(key[j * 6 + 0]); for(int i = 0; i < 31; i++) bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0; } // Append the remaining character's 4 bits sequence = (uint)codeTable.IndexOf(key[24]); bits[124] = (sequence & powersOfTwo[4, 1]) != 0; bits[125] = (sequence & powersOfTwo[3, 1]) != 0; bits[126] = (sequence & powersOfTwo[2, 1]) != 0; bits[127] = (sequence & powersOfTwo[1, 1]) != 0; // Revert the mangling that was applied to the key when encoding... unmangle(bits); // ...and we've got our GUID back! byte[] guidBytes = new byte[16]; bits.CopyTo(guidBytes, 0); return new LicenseKey(new Guid(guidBytes)); } /// Initializes a new, empty license key public LicenseKey() : this(Guid.Empty) { } /// Initializes the license key from a GUID /// GUID that is used to create the license key public LicenseKey(Guid source) { this.guid = source; } /// Accesses the four integer values within a license key /// /// When the index lies outside of the key's fields /// public int this[int index] { get { if((index < 0) || (index > 3)) throw new IndexOutOfRangeException("Index out of range"); return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4); } set { if((index < 0) || (index > 3)) throw new IndexOutOfRangeException("Index out of range"); using(MemoryStream guidBytes = new MemoryStream(this.guid.ToByteArray())) { guidBytes.Position = index * 4; new BinaryWriter(guidBytes).Write(value); this.guid = new Guid(guidBytes.ToArray()); } } } /// Converts the license key into a GUID /// The GUID created from the license key public Guid ToGuid() { return this.guid; } /// Converts the license key into a byte array /// A byte array containing the converted license key public byte[] ToByteArray() { return this.guid.ToByteArray(); } /// Converts the license key to a string /// A string containing the converted license key public override string ToString() { StringBuilder resultBuilder = new StringBuilder(); // Build a bit array from the input data BitArray bits = new BitArray(this.guid.ToByteArray()); mangle(bits); int sequence = 0; // Build 4 sequences of 6 characters from the first 124 bits for(int i = 0; i < 4; ++i) { // We take the next 31 bits from the buffer for(int j = 0; j < 31; ++j) sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0]; // Using 7 bits, a number up to 2.147.483.648 can be represented, // while 6 alpha-numerical characters allow for 2.176.782.336 possible values, // which is enough to fit 7 bits into each 6 alpha-numerical characters // nun in 6 alphanumerische Zeichen zu verpacken. for(int j = 0; j < 6; ++j) { resultBuilder.Append(codeTable[sequence % 36]); sequence /= 36; } } // Use the remaining 4 bits to build the final character resultBuilder.Append( codeTable[ (int)( powersOfTwo[4, bits[124] ? 1 : 0] | powersOfTwo[3, bits[125] ? 1 : 0] | powersOfTwo[2, bits[126] ? 1 : 0] | powersOfTwo[1, bits[127] ? 1 : 0] | powersOfTwo[0, 1] // One bit remains unused :) ) ] ); // Now build a nice, readable string from the decoded characters string s = resultBuilder.ToString(); return s.Substring(0, 5) + "-" + s.Substring(5, 5) + "-" + s.Substring(10, 5) + "-" + s.Substring(15, 5) + "-" + s.Substring(20, 5); } /// Mangles a bit array /// Bit array that will be mangled private static void mangle(BitArray bits) { BitArray temp = new BitArray(bits); for(int i = 0; i < temp.Length; ++i) { bits[i] = temp[shuffle[i]]; if((i & 1) != 0) bits[i] = !bits[i]; } } /// Unmangles a bit array /// Bit array that will be unmangled private static void unmangle(BitArray bits) { BitArray temp = new BitArray(bits); for(int i = 0; i < temp.Length; ++i) { if((i & 1) != 0) temp[i] = !temp[i]; bits[shuffle[i]] = temp[i]; } } /// Table with the individual characters in a key private static readonly string codeTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// Helper array with the powers of two private static readonly uint[,] powersOfTwo = new uint[32, 2] { { 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 }, { 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 }, { 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 }, { 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 }, { 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 }, { 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 }, { 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 }, { 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 } }; /// Index list for rotating the bit arrays private static readonly byte[] shuffle = new byte[128] { 99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48, 33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14, 92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108, 5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7, 56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9, 73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17, 112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63, 11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58 }; /// GUID in which the key is stored private Guid guid; } } // namespace Nuclex.Support.Licensing