#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
using System;
using System.Collections;
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");
// Convert the GUID into binary data so we can replace one of its values
byte[] guidBytes = this.guid.ToByteArray();
// Overwrite the section at the index specified by the user with the new value
Array.Copy(
BitConverter.GetBytes(value), 0, // source and start index
guidBytes, index * 4, // destination and start index
4 // length
);
// Replacement finished, now we can reconstruct our guid
this.guid = new Guid(guidBytes);
}
}
/// 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 31 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 means we can fit 31 bits into every 6 alpha-numerical characters.
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
resultBuilder.Insert(5, keyDelimiter, 0, 1);
resultBuilder.Insert(11, keyDelimiter, 0, 1);
resultBuilder.Insert(17, keyDelimiter, 0, 1);
resultBuilder.Insert(23, keyDelimiter, 0, 1);
return resultBuilder.ToString();
}
/// 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];
}
}
/// Character used to delimit each 5 digit group in a license key
///
/// Required to be a char array because the .NET Compact Framework only provides
/// an overload for char[] in the StringBuilder.Insert() method.
///
private static char[] keyDelimiter = new char[] { '-' };
/// Table with the individual characters in a key
private static readonly string codeTable =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/// Helper array containing the precalculated 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