Skip to content

Instantly share code, notes, and snippets.

@WitherOrNot
Last active April 5, 2025 22:28
Show Gist options
  • Save WitherOrNot/455c922849aed6cb330eb9d5d656005c to your computer and use it in GitHub Desktop.
Save WitherOrNot/455c922849aed6cb330eb9d5d656005c to your computer and use it in GitHub Desktop.
Extended Confirmation ID generation for Windows and non-Windows products
Display the source blob
Display the rendered blob
Raw
{
"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