Skip to content

Instantly share code, notes, and snippets.

@qffdn
Created July 15, 2017 06:11
Show Gist options
  • Save qffdn/bd20f9b540b3bf63ff2ea2bd11bf7b96 to your computer and use it in GitHub Desktop.
Save qffdn/bd20f9b540b3bf63ff2ea2bd11bf7b96 to your computer and use it in GitHub Desktop.
Microsoft/Windows product key decoder
/*
* Copyright (c) 2017 Donald Smith <qffdn@cock.li>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <ctype.h>
#include <err.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BINARY_LENGTH 15
#define ISNEWBINKEY(binkey) (binkey[14] & 0x08)
static uint32_t crc(const unsigned char *buf, size_t len);
static void decode(char *key, unsigned char *binkey);
static void normalize(char *key);
static void strip(char *key);
static unsigned int translatealpha(int c);
static void uppercase(char *key);
static void usage(void);
static void printcomposite(const unsigned char *binkey);
static void verifycrc(const unsigned char *binkey);
/* Table for ISO/IEC 8802-3:1996 aka POSIX 1003.2 cksum */
static uint32_t crctable[] = {
0x00000000,
0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
/*
* Prints usage for this program.
*/
static void __attribute__((__noreturn__))
usage(void)
{
fprintf(stderr, "usage: mpkdec productkey\n");
exit(1);
}
/*
* Strips the dashes from a product key after verifying the product key length.
*/
static void
strip(char *key)
{
size_t len;
if ((len = strlen(key)) != 29) {
/*
* Pre-stripped keys are accepted in case of overly cooperative
* users, but this not hinted in the error message as an attempt
* to ensure well-formed input and avoid confusion.
*/
if (len == 25)
return;
errx(1, "key has invalid length (expected 29)");
}
if (key[5] != '-' || key[11] != '-' || key[17] != '-' || key[23] != '-')
errx(1, "malformed key (dashes missing or misplaced)");
memmove(key + 5, key + 6, 5);
memmove(key + 10, key + 12, 5);
memmove(key + 15, key + 18, 5);
memmove(key + 20, key + 24, 5);
key[25] = '\0';
}
/*
* Converts a string to upper case.
*/
static void
uppercase(char *key)
{
for (; *key != '\0'; ++key)
*key = (char)toupper(*key);
}
/*
* Normalizes a product key. This strips the dashes from it and converts it to
* upper case.
*/
static void
normalize(char *key)
{
strip(key);
uppercase(key);
}
/*
* Translates a product key character to its base24 equivalent value.
*/
static unsigned int
translatealpha(int c)
{
switch (c) {
case 'B':
return 0;
case 'C':
return 1;
case 'D':
return 2;
case 'F':
return 3;
case 'G':
return 4;
case 'H':
return 5;
case 'J':
return 6;
case 'K':
return 7;
case 'M':
return 8;
case 'P':
return 9;
case 'Q':
return 10;
case 'R':
return 11;
case 'T':
return 12;
case 'V':
return 13;
case 'W':
return 14;
case 'X':
return 15;
case 'Y':
return 16;
case '2':
return 17;
case '3':
return 18;
case '4':
return 19;
case '6':
return 20;
case '7':
return 21;
case '8':
return 22;
case '9':
return 23;
default:
errx(1, "invalid character %c", c);
}
}
/*
* Decodes a product key into its binary form.
*
* This does simple base24 decoding. If an N is found in the product key, the
* offset of the N in the product key is used as the first character for
* decoding. Multiple instances of N are invalid.
*/
static void
decode(char *key, unsigned char *binkey)
{
const char *alphabet = "BCDFGHJKMPQRTVWXY2346789";
const size_t alphabetlen = strlen(alphabet);
unsigned int c, i, j;
char *newkeymarker;
memset(binkey, 0, BINARY_LENGTH);
if ((newkeymarker = strchr(key, 'N')) != NULL) {
c = (unsigned int)(newkeymarker - key);
if (c >= alphabetlen)
errx(1, "invalid key: position of character N invalid");
memmove(key + 1, key, c);
key[0] = alphabet[c];
}
for (i = 0; i < 25; ++i) {
c = translatealpha(key[i]);
/*
* This code should be equivalent to:
* accumulator = accumulator * base;
* accumulator += translated_char;
*/
for (j = 0; j < BINARY_LENGTH; ++j) {
c += binkey[j] * alphabetlen;
binkey[j] = c & 255;
c >>= 8;
}
}
if (newkeymarker != NULL)
binkey[14] |= 0x08;
}
/*
* Calculates a CRC checksum using the ISO/IEC 8802-3:1996 aka
* POSIX 1003.2 cksum algorithm.
*/
static uint32_t
crc(const unsigned char *buf, size_t len)
{
size_t i;
uint32_t crc = 0xFFFFFFFF;
for (i = 0; i < len; ++i)
crc = (crc << 8) ^ crctable[((crc >> 24) ^ buf[i]) & 0xff];
return ~crc;
}
/*
* Verifies the built-in checksum for newer (Windows 8 and above) product keys.
*/
static void
verifycrc(const unsigned char *binkey)
{
unsigned short theirs, mine;
unsigned char clamped[BINARY_LENGTH + 1];
memcpy(clamped, binkey, BINARY_LENGTH);
clamped[12] &= 0x7f;
clamped[13] = 0;
clamped[14] &= 0x06;
/* Microsoft's checksum algorithm expects an extraneous 0 byte */
clamped[15] = 0;
theirs = ((((binkey[14] << 16)
| (binkey[13] << 8)
| (binkey[12]))) >> 7) & 0x3ff;
mine = crc(clamped, sizeof(clamped)) & 0x3ff;
if (mine != theirs)
errx(1, "CRC mismatch (calculated: %hu, in key: %hu)",
mine, theirs);
}
/*
* Prints the composite number for newer keys (starting with Windows 8), i.e.
* those containing an N.
*/
static void
printcomposite(const unsigned char *binkey)
{
unsigned long c = (((binkey[5] << 24)
| (binkey[4] << 16)
| (binkey[3] << 8)
| binkey[2])
>> 4) & 0x3fffffff;
printf("Composite: %03lu-%06lu\n", c / 1000000, c % 1000000);
}
/*
* mpkdec decodes Microsoft product keys into their binary form and prints the
* decoded binary key as hexadecimal.
*
* Keys used in newer versions of Windows starting with Windows 8, i.e. those
* containing an N anywhere in them, will also have their built-in checksum
* verified. An error is printed to stderr if the checksum mismatches.
*
* The output for keys in the era of Windows Vista and 7 will be garbage. This
* is because the keys have additional obfuscation.
*
* It will read and display the composite number for keys containing an N.
* This is not possible for older keys because the composite position varies
* wildly.
*/
int
main(int argc, char *argv[])
{
unsigned int i;
unsigned char binkey[BINARY_LENGTH];
char *key;
#ifdef __OPENBSD__
if (pledge("stdio", NULL) == -1)
err(1, "pledge");
#endif
if (argc < 2)
usage();
key = argv[1];
normalize(key);
decode(key, binkey);
printf("Decoded bytes: ");
for (i = 0; i < BINARY_LENGTH; ++i)
printf("%02x", binkey[i]);
puts("");
if (ISNEWBINKEY(binkey)) {
verifycrc(binkey);
printcomposite(binkey);
}
return 0;
}
@rolfen
Copy link

rolfen commented Apr 2, 2018

Thank you. I compiled it with gcc and tried it.
Can any product/license information be derived from the output? What is the relation to product ID?
Nice work.

@boryso2
Copy link

boryso2 commented Aug 14, 2018

Can you add code that generates crc and composite for oldest product keys (Windows 7)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment