{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Product Key Generator - Windows XP" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Paste JSON object for BINK data here:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Windows XP Professional Retail (Bink ID 2C)\n", "key_data = {\n", " \"p\": 24412280675538104642884792561502783185577987209710041026341163083973933860854736635268965257725055809364646140091249,\n", " \"a\": 1,\n", " \"b\": 0,\n", " \"B\": [\n", " 21673361717619259910600499419800485528178801849923454062050055236231939594233283543796077751210469045350919066368895,\n", " 5232476492611604888729825305639232005017822876108144652169892952989580351454246958886421453535493897842819359154864\n", " ],\n", " \"K\": [\n", " 21551722775458524408480112576069559265917312687549112053580919391285918530584174752292844347621326558272739603979057,\n", " 13463977158522661542654520438933687107907187215503371589980428235633526671841388652148099285621876350916055100879930\n", " ],\n", " \"order\": 55681564377333977,\n", " \"private_key\": 30951839223306173\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run this cell to generate key" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "import hashlib\n", "\n", "# p = order of field Fp\n", "# Fp = Galois field of order p\n", "# E = Elliptic curve y^2 = x^3 + ax + b over Fp\n", "# B = generator on E\n", "# K = inverse of public key\n", "# order = order of E\n", "\n", "p = key_data[\"p\"]\n", "Fp = GF(p)\n", "E = EllipticCurve(Fp, [0, 0, 0, key_data[\"a\"], key_data[\"b\"]])\n", "B = E.point(key_data[\"B\"])\n", "K = E.point(key_data[\"K\"])\n", "order = key_data[\"order\"]\n", "private_key = -key_data[\"private_key\"] % order\n", "\n", "# PID of product key\n", "pid = 756_696969\n", "\n", "# Key alphabet\n", "KCHARS = \"BCDFGHJKMPQRTVWXY2346789\"\n", "\n", "def int_to_bytes(n, l=None):\n", " n = int(n)\n", " \n", " if not l:\n", " l = (n.bit_length() + 7) // 8\n", " \n", " return n.to_bytes(l, byteorder=\"little\")\n", "\n", "def encode_pkey(n):\n", " out = \"\"\n", " \n", " while n > 0:\n", " out = KCHARS[n % 24] + out\n", " n //= 24\n", " \n", " out = \"-\".join([out[i:i+5] for i in range(0, len(out), 5)])\n", " return out\n", "\n", "pid <<= 1\n", "\n", "while True:\n", " k = getrandbits(384)\n", " r = k * B\n", " x, y = r.xy()\n", "\n", " md = hashlib.sha1(int_to_bytes(pid, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()\n", " h = int.from_bytes(md[:4], byteorder=\"little\") >> 4\n", " h &= 0xfffffff\n", "\n", " s = int(abs((private_key * h + k) % order))\n", " raw_pkey = s << 59 | h << 31 | pid\n", " \n", " print(hex(pid)[2:], hex(h)[2:], hex(s)[2:], hex(raw_pkey)[2:])\n", " \n", " if raw_pkey >> 96 < 0x40000:\n", " break\n", "\n", "print(encode_pkey(raw_pkey))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Key decoder (run above cell first)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def decode_pkey(k):\n", " k = k.replace(\"-\", \"\")\n", " out = 0\n", " \n", " for c in k:\n", " out *= 24\n", " out += KCHARS.index(c)\n", " \n", " return out\n", "\n", "pkey = input(\"Product Key (dashes optional): \")\n", "raw_pkey = decode_pkey(pkey)\n", "\n", "kpid = (raw_pkey & 0x7fffffff) >> 1\n", "verify = (kpid // 1000000) == ((pid >> 1) // 1000000)\n", "print(kpid, pid >> 1)\n", "\n", "if verify:\n", " h = (raw_pkey >> 31) & 0xfffffff\n", " s = (raw_pkey >> 59) & 0x7ffffffffffffff\n", "\n", " r = h * K + s * B\n", " x, y = r.xy()\n", "\n", " md = hashlib.sha1(int_to_bytes(kpid << 1, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()\n", " hp = int.from_bytes(md[:4], byteorder=\"little\") >> 4\n", " hp &= 0xfffffff\n", "\n", " print(h, hp)\n", " \n", " if h == hp:\n", " print(\"Valid key\")\n", " else:\n", " print(\"Invalid key\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Confirmation ID generator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import hashlib\n", "\n", "# order of field Fp \n", "p = 0x16A6B036D7F2A79\n", "# Galois field of order p\n", "Fp = GF(p)\n", "# Polynomial field Fp[x] over Fp\n", "Fpx. = Fp[]\n", "# Hyperellptic curve function\n", "F = x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x\n", "# Hyperelliptic curve E: y^2 = F(x) over Fp\n", "E = HyperellipticCurve(F)\n", "# The jacobian over E\n", "J = E.jacobian()\n", "\n", "# This constant inverts multiplication by 0x1001 in verification\n", "# My best guess for how it was calculated: INV = 0x10001^-1 (mod |J|)\n", "# |J| is hard to compute, how can we calculate for other curves?\n", "INV = 0x40DA7C36D44C04E21B9D10F127C1\n", "\n", "# Key to decrypt installation IDs\n", "IID_KEY = b'\\x6A\\xC8\\x5E\\xD4'\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(\"\".join(parts))\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", "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 << 25) - 1)\n", "version = (productid >> 52) & 7\n", "pid4 = productid >> 55\n", "\n", "assert version == (4 if len(iid) == 17 else 5)\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", "\n", "print(\"\\nConfirmation IDs:\")\n", "\n", "for i in range(0x81):\n", " data[7] = 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) * 43)\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]) / 43).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": [ "Confirmation ID decoder/validator (made by diamondggg)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import hashlib\n", "\n", "# 226512-274743-842923-777124-961370-722240-570042-517722-757426\n", "installationId = 114535500880440159787527912804896629001083118\n", "installationIdSize = 19 # 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", "confirmationId = 15771960290497900806797040541467113\n", "# 022032-220754-159721-909624-985141-504586-914001\n", "#confirmationId = 02203220751597290962985145045891400\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, b'\\x6A\\xC8\\x5E\\xD4')\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 << 25) - 1)\n", "version = (productid >> 52) & 7\n", "pid4 = productid >> 55\n", "\n", "assert version == (4 if len(iid) == 17 else 5)\n", "\n", "key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')\n", "# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids\n", "\n", "# now the math\n", "\n", "p = 0x16A6B036D7F2A79\n", "Fp = GF(p)\n", "Fpx. = Fp[]\n", "E = HyperellipticCurve(x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x)\n", "J = E.jacobian()\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)-43*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])/43)\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)" ] } ], "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": 5 }