Last active
April 5, 2025 22:28
-
-
Save WitherOrNot/455c922849aed6cb330eb9d5d656005c to your computer and use it in GitHub Desktop.
Extended Confirmation ID generation for Windows and non-Windows products
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Constants (run this cell first!)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"\"\"\"\n", | |
"# MS Plus! DME\n", | |
"\n", | |
"# order of field Fp \n", | |
"p = 0x16A5DABA0605983\n", | |
"# Coefficients of F\n", | |
"coeffs = [0x334f24f75caa0e, 0x1392ff62889bd7b, 0x135131863ba2db8, 0x153208e78006010, 0x163694f26056db, 1]\n", | |
"# This constant inverts multiplication by 0x10001 in verification\n", | |
"INV = 0x01c61212ece6107c4254c43a5d1181\n", | |
"# Key to decrypt installation IDs\n", | |
"IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", | |
"#\"\"\"\n", | |
"\n", | |
"#\"\"\"\n", | |
"# Office XP/2003\n", | |
"\n", | |
"# order of field Fp \n", | |
"p = 0x16E48DD18451FE9\n", | |
"# Coefficients of F\n", | |
"coeffs = [0, 0xE5F5ECD95C8FD2, 0xFF28276F11F61, 0xFB2BD9132627E6, 0xE5F5ECD95C8FD2, 1]\n", | |
"# This constant inverts multiplication by 0x10001 in verification\n", | |
"INV = 0x01fb8cf48a70dfefe0302a1f7a5341\n", | |
"# Key to decrypt installation IDs\n", | |
"IID_KEY = b'\\x5A\\x30\\xB9\\xF3'\n", | |
"#\"\"\"\n", | |
"\n", | |
"\"\"\"\n", | |
"# Whistler 2428 (could be others)\n", | |
"\n", | |
"# order of field Fp \n", | |
"p = 0x16BD82821354FA3\n", | |
"# Coefficients of F\n", | |
"coeffs = [0, 0xDEFD8C5651954F, 0xA23AA12556ECE5, 0x89D79AD61B786D, 0xCCA087F0A6A4A4, 1]\n", | |
"# This constant inverts multiplication by 0x10001 in verification\n", | |
"INV = 0xd9ed873ed84a45761c23fd7fafd1\n", | |
"# Key to decrypt installation IDs\n", | |
"IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", | |
"#\"\"\"\n", | |
"\n", | |
"\n", | |
"\"\"\"\n", | |
"# Windows XP/Server 2003/Longhorn Pre-Reset\n", | |
"\n", | |
"# order of field Fp \n", | |
"p = 0x16A6B036D7F2A79\n", | |
"# Coefficients of F\n", | |
"coeffs = [0, 0x21840136C85381, 0x44197B83892AD0, 0x1400606322B3B04, 0x1400606322B3B04, 1]\n", | |
"# This constant inverts multiplication by 0x10001 in verification\n", | |
"INV = 0x40DA7C36D44C04E21B9D10F127C1\n", | |
"# Key to decrypt installation IDs\n", | |
"IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\n", | |
"#\"\"\"\n", | |
"\n", | |
"# minimal quadratic non-residue of p\n", | |
"mqnr = least_quadratic_nonresidue(p)\n", | |
"# Galois field of order p\n", | |
"Fp = GF(p)\n", | |
"# Polynomial field Fp[x] over Fp\n", | |
"Fpx.<x> = Fp[]\n", | |
"\n", | |
"# Hyperellptic curve function\n", | |
"F = sum(k*x^i for i, k in enumerate(coeffs))\n", | |
"# Hyperelliptic curve E: y^2 = F(x) over Fp\n", | |
"E = HyperellipticCurve(F)\n", | |
"# The jacobian over E\n", | |
"J = E.jacobian()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Generate Confirmation ID" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"scrolled": false | |
}, | |
"outputs": [], | |
"source": [ | |
"import hashlib\n", | |
"\n", | |
"# Validate installation ID checksum\n", | |
"def validate_cksum(n):\n", | |
" print(\"Checksumming installation ID...\")\n", | |
" n = n.replace(\"-\", \"\")\n", | |
"\n", | |
" cksum = 0\n", | |
" for i, k in enumerate(map(int, n)):\n", | |
" if (i + 1) % 6 == 0 or i == len(n) - 1:\n", | |
" print(\"Expected last digit\", cksum % 7, \"got\", k)\n", | |
" if cksum % 7 != k:\n", | |
" return None\n", | |
" \n", | |
" cksum = 0\n", | |
" else:\n", | |
" cksum += k * (i % 2 + 1)\n", | |
" \n", | |
" parts = [n[i:i+5] for i in range(0, len(n), 6)]\n", | |
" n_out = \"\".join(parts)\n", | |
" \n", | |
" if len(n_out) == 42:\n", | |
" n_out = n_out[:-1]\n", | |
" \n", | |
" if len(n_out) != 45 and len(n_out) != 41:\n", | |
" return None\n", | |
" \n", | |
" return int(n_out)\n", | |
"\n", | |
"# Insert checksum digits into confirmation ID\n", | |
"def add_cksum(n):\n", | |
" cksums = []\n", | |
" n = str(n).zfill(35)\n", | |
" parts = [n[i:i+5] for i in range(0, len(n), 5)]\n", | |
" \n", | |
" for p in parts:\n", | |
" cksum = 0\n", | |
" \n", | |
" for i, k in enumerate(map(int, p)):\n", | |
" cksum += k * (i % 2 + 1)\n", | |
" \n", | |
" cksums.append(str(cksum % 7))\n", | |
" \n", | |
" n_out = \"\"\n", | |
" \n", | |
" for i in range(7):\n", | |
" n_out += parts[i] + cksums[i] + (\"-\" if i != 6 else \"\")\n", | |
" \n", | |
" return n_out\n", | |
"\n", | |
"def encrypt(decrypted, key):\n", | |
" size_half = len(decrypted) // 2\n", | |
" size_half_dwords = size_half - (size_half % 4)\n", | |
" last = decrypted[size_half*2:]\n", | |
" decrypted = decrypted[:size_half*2]\n", | |
" for i in range(4):\n", | |
" first = decrypted[:size_half]\n", | |
" second = decrypted[size_half:]\n", | |
" sha1_result = hashlib.sha1(second + key).digest()\n", | |
" sha1_result = (sha1_result[:size_half_dwords] +\n", | |
" sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", | |
" decrypted = second + bytes(x^^y for x,y in zip(first, sha1_result))\n", | |
" return decrypted + last\n", | |
"\n", | |
"def decrypt(encrypted, key):\n", | |
" size_half = len(encrypted) // 2\n", | |
" size_half_dwords = size_half - (size_half % 4)\n", | |
" last = encrypted[size_half*2:]\n", | |
" encrypted = encrypted[:size_half*2]\n", | |
" for i in range(4):\n", | |
" first = encrypted[:size_half]\n", | |
" second = encrypted[size_half:]\n", | |
" sha1_result = hashlib.sha1(first + key).digest()\n", | |
" sha1_result = (sha1_result[:size_half_dwords] +\n", | |
" sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", | |
" encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first\n", | |
" return encrypted + last\n", | |
"\n", | |
"# Find v of divisor (u, v) of curve y^2 = F(x)\n", | |
"def find_v(u):\n", | |
" f = F % u\n", | |
" c2 = u[1]^2 - 4 * u[0]\n", | |
" c1 = 2 * f[0] - f[1] * u[1]\n", | |
" \n", | |
" if c2 == 0:\n", | |
" if c1 == 0:\n", | |
" return None\n", | |
" \n", | |
" try:\n", | |
" v1 = sqrt(f[1]^2 / (2 * c1))\n", | |
" v1.lift()\n", | |
" except:\n", | |
" return None\n", | |
" else:\n", | |
" try:\n", | |
" d = 2 * sqrt(f[0]^2 + f[1] * (f[1] * u[0] - f[0] * u[1]))\n", | |
" v1_1 = sqrt((c1 - d)/c2)\n", | |
" v1_2 = sqrt((c1 + d)/c2)\n", | |
" except:\n", | |
" return None\n", | |
"\n", | |
" try:\n", | |
" v1_1.lift()\n", | |
" v1 = v1_1\n", | |
" except:\n", | |
" try:\n", | |
" v1_2.lift()\n", | |
" v1 = v1_2\n", | |
" except:\n", | |
" return None\n", | |
" \n", | |
" v0 = (f[1] + u[1] * v1^2) / (2 * v1)\n", | |
" v = v0 + v1 * x\n", | |
" \n", | |
" assert (v^2 - f) % u == 0\n", | |
" return v\n", | |
"\n", | |
"# unpack&decrypt installationId\n", | |
"installationId = validate_cksum(input(\"Installation ID (dashes optional): \"))\n", | |
"# installationId = 11234597509478704096883784033789146715149\n", | |
"print(installationId)\n", | |
"\n", | |
"if not installationId:\n", | |
" raise Exception(\"Invalid Installation ID (checksum fail)\")\n", | |
"\n", | |
"installationIdSize = 19 if len(str(installationId)) > 41 else 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))\n", | |
"iid = int(installationId).to_bytes(installationIdSize, byteorder='little')\n", | |
"iid = decrypt(iid, IID_KEY)\n", | |
"hwid = iid[:8]\n", | |
"productid = int.from_bytes(iid[8:17], byteorder='little')\n", | |
"productkeyhash = iid[17:]\n", | |
"pid1 = productid & ((1 << 17) - 1)\n", | |
"pid2 = (productid >> 17) & ((1 << 10) - 1)\n", | |
"pid3 = (productid >> 27) & ((1 << 24) - 1)\n", | |
"version = (productid >> 52) & 7\n", | |
"pid4 = productid >> 55\n", | |
"\n", | |
"if version != (4 if len(iid) == 17 else 5):\n", | |
" print(f\"Invalid Installation ID (unknown version {version})\")\n", | |
"\n", | |
"print(installationIdSize)\n", | |
"print(pid1, pid2, pid3, pid4)\n", | |
"\n", | |
"key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", | |
"\n", | |
"data = [0x00] * 14\n", | |
"# data = b'\\xb9g\\xdd\\xe1\\xb0\\xef-\\x1e\\xbd\\x0frE\\xd8\\xbe'\n", | |
"print(\"\\nConfirmation IDs:\")\n", | |
"\n", | |
"for i in range(0x81):\n", | |
" data[4] = i\n", | |
" # Encrypt conf ID, find u of divisor (u, v)\n", | |
" encrypted = encrypt(bytes(data), key)\n", | |
" encrypted = int.from_bytes(encrypted, byteorder=\"little\")\n", | |
" x1, x2 = Fp(encrypted % p), Fp((encrypted // p) + 1)\n", | |
" u1, u0 = x1 * 2, (x1 ^ 2) - ((x2 ^ 2) * mqnr)\n", | |
" u = x^2 + u1 * x + u0\n", | |
"\n", | |
" # Generate original divisor\n", | |
" v = find_v(u)\n", | |
" \n", | |
" if not v:\n", | |
" continue\n", | |
" \n", | |
" d2 = J(u, v)\n", | |
" divisor = d2 * INV\n", | |
" \n", | |
" # Get x1 and x2\n", | |
" roots = [x for x, y in divisor[0].roots()]\n", | |
"\n", | |
" if len(roots) > 0:\n", | |
" y = [divisor[1](r) for r in roots]\n", | |
" x1 = (-roots[0]).lift()\n", | |
" x2 = (-roots[1]).lift()\n", | |
"\n", | |
" if (x1 > x2) or (y[0].lift() % 2 != y[1].lift() % 2):\n", | |
" x1 = (-roots[1]).lift()\n", | |
" x2 = (-roots[0]).lift()\n", | |
" else:\n", | |
" x2 = (divisor[0][1] / 2).lift()\n", | |
" x1 = sqrt((x2^2 - divisor[0][0]) / mqnr).lift() + p\n", | |
"\n", | |
" # Win\n", | |
" conf_id = x1 * (p + 1) + x2\n", | |
" conf_id = add_cksum(conf_id)\n", | |
" print(conf_id)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Validate Confirmation ID (originally by diamondggg)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [], | |
"source": [ | |
"import hashlib\n", | |
"\n", | |
"# 226512-274743-842923-777124-961370-722240-570042-517722-757426\n", | |
"installationId = 44706039542602728435285810860722693781\n", | |
"installationIdSize = 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))\n", | |
"# all three of following are valid generated\n", | |
"# 013705-060122-603141-961392-086136-909901-494476\n", | |
"confid = input(\"Confirmation ID: \").replace(\"-\", \"\")\n", | |
"confirmationId = int(\"\".join([confid[i:i+5] for i in range(0, len(confid), 6)]))\n", | |
"# confirmationId = 13009861034010972507754924748629391\n", | |
"print(confirmationId)\n", | |
"# 022032-220754-159721-909624-985141-504586-914001\n", | |
"#confirmationId = 2203220751597290962985145045891400\n", | |
"# 137616-847280-708585-827476-874935-313366-790880\n", | |
"#confirmationId = 13761847287085882747874933133679088\n", | |
"\n", | |
"def decrypt(encrypted, key):\n", | |
" size_half = len(encrypted) // 2\n", | |
" size_half_dwords = size_half - (size_half % 4)\n", | |
" last = encrypted[size_half*2:]\n", | |
" encrypted = encrypted[:size_half*2]\n", | |
" for i in range(4):\n", | |
" first = encrypted[:size_half]\n", | |
" second = encrypted[size_half:]\n", | |
" sha1_result = hashlib.sha1(first + key).digest()\n", | |
" sha1_result = (sha1_result[:size_half_dwords] +\n", | |
" sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])\n", | |
" encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first\n", | |
" return encrypted + last\n", | |
"\n", | |
"# unpack&decrypt installationId\n", | |
"iid = int(installationId).to_bytes(installationIdSize, byteorder='little')\n", | |
"iid = decrypt(iid, IID_KEY)\n", | |
"hwid = iid[:8]\n", | |
"productid = int.from_bytes(iid[8:17], byteorder='little')\n", | |
"# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids\n", | |
"productkeyhash = iid[17:]\n", | |
"pid1 = productid & ((1 << 17) - 1)\n", | |
"pid2 = (productid >> 17) & ((1 << 10) - 1)\n", | |
"pid3 = (productid >> 27) & ((1 << 24) - 1)\n", | |
"version = (productid >> 52) & 7\n", | |
"pid4 = productid >> 55\n", | |
"\n", | |
"print(pid1, pid2, pid3, pid4)\n", | |
"\n", | |
"if version != (4 if len(iid) == 17 else 5):\n", | |
" print(version)\n", | |
"\n", | |
"key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", | |
"\n", | |
"# deserialize divisor\n", | |
"x1 = confirmationId // (p + 1)\n", | |
"x2 = confirmationId % (p + 1)\n", | |
"if x1 <= p:\n", | |
" # two or less points over GF(p)\n", | |
" point1 = E.lift_x(Fp(-x1)) if x1 != p else None\n", | |
" point2 = E.lift_x(Fp(-x2)) if x2 != p else None\n", | |
" if point1 is not None and point2 is not None:\n", | |
" # there are 4 variants of how lift_x() could select both y-s\n", | |
" # we don't distinguish D and -D, but this still leaves 2 variants\n", | |
" # the chosen one is encoded by order of x1 <=> x2\n", | |
" lastbit1 = point1[1].lift() & 1\n", | |
" lastbit2 = point2[1].lift() & 1\n", | |
" if x2 < x1:\n", | |
" if lastbit1 == lastbit2:\n", | |
" point2 = E(point2[0], -point2[1])\n", | |
" else:\n", | |
" if lastbit1 != lastbit2:\n", | |
" point2 = E(point2[0], -point2[1])\n", | |
" point1 = J(point1) if point1 is not None else J(0)\n", | |
" point2 = J(point2) if point2 is not None else J(0)\n", | |
" divisor = point1 + point2\n", | |
"else:\n", | |
" # a pair of conjugate points over GF(p^2)\n", | |
" f = (x+x2)*(x+x2) - mqnr*x1*x1 # 43 is the minimal quadratic non-residue in Fp\n", | |
" Fp2 = GF(p^2)\n", | |
" point1 = E.lift_x(f.roots(Fp2)[0][0])\n", | |
" point2 = E(Fp2)(point1[0].conjugate(), point1[1].conjugate())\n", | |
" divisor = J(Fp2)(point1) + J(Fp2)(point2)\n", | |
" divisor = J(Fpx(divisor[0]), Fpx(divisor[1])) #return from Fp2 to Fp\n", | |
"\n", | |
"d2 = divisor * 0x10001\n", | |
"assert d2[0].degree() == 2\n", | |
"x1 = d2[0][1]/2\n", | |
"x2 = sqrt((x1*x1-d2[0][0])/mqnr)\n", | |
"\n", | |
"encrypted = x1.lift() + (x2.lift() - 1) * p\n", | |
"encrypted = int(encrypted).to_bytes(14,byteorder='little')\n", | |
"\n", | |
"# end of the math\n", | |
"decrypted = decrypt(encrypted, key)\n", | |
"print(decrypted.hex())\n", | |
"# 0000000000000001000000000000 for the first confirmationId\n", | |
"# 0000000000000002000000000000 for the second confirmationId\n", | |
"# 0000000000000006000000000000 for the last confirmationId\n", | |
"assert decrypted[8:] == b'\\0' * 6\n", | |
"assert decrypted[7] <= 0x80\n", | |
"# all zeroes in decrypted[0:7] are okay for the checker\n", | |
"# more precisely: if decrypted[6] == 0, first 6 bytes can be anything\n", | |
"# otherwise, decrypted[0] = length, and decrypted[1:1+length] must match first length bytes of sha1(product key)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "SageMath 9.0", | |
"language": "sage", | |
"name": "sagemath" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.8.10" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment