Files
WindowsXPKg/extras/winxp_act.ipynb
T
TheTank20andWitherOrNot f7427792e3 Add Wither's gists as references (#137)
* Add Wither's gists as references

* remove broken ones

---------

Co-authored-by: WitherOrNot <damemem@gmail.com>
2025-07-09 20:12:49 -05:00

18 KiB

Product Key Generator - Windows XP

Paste JSON object for BINK data here:

In [ ]:
# Windows XP Professional Retail (Bink ID 2C)
key_data = {
    "p": 24412280675538104642884792561502783185577987209710041026341163083973933860854736635268965257725055809364646140091249,
    "a": 1,
    "b": 0,
    "B": [
        21673361717619259910600499419800485528178801849923454062050055236231939594233283543796077751210469045350919066368895,
        5232476492611604888729825305639232005017822876108144652169892952989580351454246958886421453535493897842819359154864
    ],
    "K": [
        21551722775458524408480112576069559265917312687549112053580919391285918530584174752292844347621326558272739603979057,
        13463977158522661542654520438933687107907187215503371589980428235633526671841388652148099285621876350916055100879930
    ],
    "order": 55681564377333977,
    "private_key": 30951839223306173
}

Run this cell to generate key

In [ ]:
import hashlib

# p = order of field Fp
# Fp = Galois field of order p
# E = Elliptic curve y^2 = x^3 + ax + b over Fp
# B = generator on E
# K = inverse of public key
# order = order of E

p = key_data["p"]
Fp = GF(p)
E = EllipticCurve(Fp, [0, 0, 0, key_data["a"], key_data["b"]])
B = E.point(key_data["B"])
K = E.point(key_data["K"])
order = key_data["order"]
private_key = -key_data["private_key"] % order

# PID of product key
pid = 756_696969

# Key alphabet
KCHARS = "BCDFGHJKMPQRTVWXY2346789"

def int_to_bytes(n, l=None):
    n = int(n)
    
    if not l:
        l = (n.bit_length() + 7) // 8
    
    return n.to_bytes(l, byteorder="little")

def encode_pkey(n):
    out = ""
    
    while n > 0:
        out = KCHARS[n % 24] + out
        n //= 24
    
    out = "-".join([out[i:i+5] for i in range(0, len(out), 5)])
    return out

pid <<= 1

while True:
    k = getrandbits(384)
    r = k * B
    x, y = r.xy()

    md = hashlib.sha1(int_to_bytes(pid, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()
    h = int.from_bytes(md[:4], byteorder="little") >> 4
    h &= 0xfffffff

    s = int(abs((private_key * h + k) % order))
    raw_pkey = s << 59 | h << 31 | pid
    
    print(hex(pid)[2:], hex(h)[2:], hex(s)[2:], hex(raw_pkey)[2:])
    
    if raw_pkey >> 96 < 0x40000:
        break

print(encode_pkey(raw_pkey))

Key decoder (run above cell first)

In [ ]:
def decode_pkey(k):
    k = k.replace("-", "")
    out = 0
    
    for c in k:
        out *= 24
        out += KCHARS.index(c)
    
    return out

pkey = input("Product Key (dashes optional): ")
raw_pkey = decode_pkey(pkey)

kpid = (raw_pkey & 0x7fffffff) >> 1
verify = (kpid // 1000000) == ((pid >> 1) // 1000000)
print(kpid, pid >> 1)

if verify:
    h = (raw_pkey >> 31) & 0xfffffff
    s = (raw_pkey >> 59) & 0x7ffffffffffffff

    r = h * K + s * B
    x, y = r.xy()

    md = hashlib.sha1(int_to_bytes(kpid << 1, 4) + int_to_bytes(x, 48) + int_to_bytes(y, 48)).digest()
    hp = int.from_bytes(md[:4], byteorder="little") >> 4
    hp &= 0xfffffff

    print(h, hp)
    
    if h == hp:
        print("Valid key")
    else:
        print("Invalid key")

Confirmation ID generator

In [ ]:
import hashlib

# order of field Fp 
p = 0x16A6B036D7F2A79
# Galois field of order p
Fp = GF(p)
# Polynomial field Fp[x] over Fp
Fpx.<x> = Fp[]
# Hyperellptic curve function
F = x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x
# Hyperelliptic curve E: y^2 = F(x) over Fp
E = HyperellipticCurve(F)
# The jacobian over E
J = E.jacobian()

# This constant inverts multiplication by 0x1001 in verification
# My best guess for how it was calculated: INV = 0x10001^-1 (mod |J|)
# |J| is hard to compute, how can we calculate for other curves?
INV = 0x40DA7C36D44C04E21B9D10F127C1

# Key to decrypt installation IDs
IID_KEY = b'\x6A\xC8\x5E\xD4'

# Validate installation ID checksum
def validate_cksum(n):
    print("Checksumming installation ID...")
    n = n.replace("-", "")

    cksum = 0
    for i, k in enumerate(map(int, n)):
        if (i + 1) % 6 == 0 or i == len(n) - 1:
            print("Expected last digit", cksum % 7, "got", k)
            if cksum % 7 != k:
                return None
            
            cksum = 0
        else:
            cksum += k * (i % 2 + 1)
    
    parts = [n[i:i+5] for i in range(0, len(n), 6)]
    n_out = "".join(parts)
    
    if len(n_out) == 42:
        n_out = n_out[:-1]
    
    if len(n_out) != 45 and len(n_out) != 41:
        return None
    
    return int("".join(parts))

# Insert checksum digits into confirmation ID
def add_cksum(n):
    cksums = []
    n = str(n).zfill(35)
    parts = [n[i:i+5] for i in range(0, len(n), 5)]
    
    for p in parts:
        cksum = 0
        
        for i, k in enumerate(map(int, p)):
            cksum += k * (i % 2 + 1)
        
        cksums.append(str(cksum % 7))
    
    n_out = ""
    
    for i in range(7):
        n_out += parts[i] + cksums[i] + ("-" if i != 6 else "")
    
    return n_out

def encrypt(decrypted, key):
    size_half = len(decrypted) // 2
    size_half_dwords = size_half - (size_half % 4)
    last = decrypted[size_half*2:]
    decrypted = decrypted[:size_half*2]
    for i in range(4):
        first = decrypted[:size_half]
        second = decrypted[size_half:]
        sha1_result = hashlib.sha1(second + key).digest()
        sha1_result = (sha1_result[:size_half_dwords] +
                       sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])
        decrypted = second + bytes(x^^y for x,y in zip(first, sha1_result))
    return decrypted + last

def decrypt(encrypted, key):
    size_half = len(encrypted) // 2
    size_half_dwords = size_half - (size_half % 4)
    last = encrypted[size_half*2:]
    encrypted = encrypted[:size_half*2]
    for i in range(4):
        first = encrypted[:size_half]
        second = encrypted[size_half:]
        sha1_result = hashlib.sha1(first + key).digest()
        sha1_result = (sha1_result[:size_half_dwords] +
                       sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])
        encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first
    return encrypted + last

# Find v of divisor (u, v) of curve y^2 = F(x)
def find_v(u):
    f = F % u
    c2 = u[1]^2 - 4 * u[0]
    c1 = 2 * f[0] - f[1] * u[1]
    
    if c2 == 0:
        if c1 == 0:
            return None
        
        try:
            v1 = sqrt(f[1]^2 / (2 * c1))
            v1.lift()
        except:
            return None
    else:
        try:
            d = 2 * sqrt(f[0]^2 + f[1] * (f[1] * u[0] - f[0] * u[1]))
            v1_1 = sqrt((c1 - d)/c2)
            v1_2 = sqrt((c1 + d)/c2)
        except:
            return None

        try:
            v1_1.lift()
            v1 = v1_1
        except:
            try:
                v1_2.lift()
                v1 = v1_2
            except:
                return None
    
    v0 = (f[1] + u[1] * v1^2) / (2 * v1)
    v = v0 + v1 * x
    
    assert (v^2 - f) % u == 0
    return v

# unpack&decrypt installationId
installationId = validate_cksum(input("Installation ID (dashes optional): "))
print(installationId)

if not installationId:
    raise Exception("Invalid Installation ID (checksum fail)")

installationIdSize = 19 if len(str(installationId)) > 41 else 17 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))
iid = int(installationId).to_bytes(installationIdSize, byteorder='little')
iid = decrypt(iid, IID_KEY)
hwid = iid[:8]
productid = int.from_bytes(iid[8:17], byteorder='little')
productkeyhash = iid[17:]
pid1 = productid & ((1 << 17) - 1)
pid2 = (productid >> 17) & ((1 << 10) - 1)
pid3 = (productid >> 27) & ((1 << 25) - 1)
version = (productid >> 52) & 7
pid4 = productid >> 55

assert version == (4 if len(iid) == 17 else 5)

key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')

data = [0x00] * 14

print("\nConfirmation IDs:")

for i in range(0x81):
    data[7] = i
    # Encrypt conf ID, find u of divisor (u, v)
    encrypted = encrypt(bytes(data), key)
    encrypted = int.from_bytes(encrypted, byteorder="little")
    x1, x2 = Fp(encrypted % p), Fp((encrypted // p) + 1)
    u1, u0 = x1 * 2, (x1 ^ 2) - ((x2 ^ 2) * 43)
    u = x^2 + u1 * x + u0

    # Generate original divisor
    v = find_v(u)
    
    if not v:
        continue
    
    d2 = J(u, v)
    divisor = d2 * INV
    
    # Get x1 and x2
    roots = [x for x, y in divisor[0].roots()]

    if len(roots) > 0:
        y = [divisor[1](r) for r in roots]
        x1 = (-roots[0]).lift()
        x2 = (-roots[1]).lift()

        if (x1 > x2) or (y[0].lift() % 2 != y[1].lift() % 2):
            x1 = (-roots[1]).lift()
            x2 = (-roots[0]).lift()
    else:
        x2 = (divisor[0][1] / 2).lift()
        x1 = sqrt((x2^2 - divisor[0][0]) / 43).lift() + p

    # Win
    conf_id = x1 * (p + 1) + x2
    conf_id = add_cksum(conf_id)
    print(conf_id)

Confirmation ID decoder/validator (made by diamondggg)

In [ ]:
import hashlib

# 226512-274743-842923-777124-961370-722240-570042-517722-757426
installationId = 114535500880440159787527912804896629001083118
installationIdSize = 19 # 17 for XP Gold, 19 for SP1+ (includes 12 bits of sha1(product key))
# all three of following are valid generated
# 013705-060122-603141-961392-086136-909901-494476
confirmationId = 15771960290497900806797040541467113
# 022032-220754-159721-909624-985141-504586-914001
#confirmationId = 02203220751597290962985145045891400
# 137616-847280-708585-827476-874935-313366-790880
#confirmationId = 13761847287085882747874933133679088

def decrypt(encrypted, key):
    size_half = len(encrypted) // 2
    size_half_dwords = size_half - (size_half % 4)
    last = encrypted[size_half*2:]
    encrypted = encrypted[:size_half*2]
    for i in range(4):
        first = encrypted[:size_half]
        second = encrypted[size_half:]
        sha1_result = hashlib.sha1(first + key).digest()
        sha1_result = (sha1_result[:size_half_dwords] +
                       sha1_result[size_half_dwords+4-(size_half%4) : size_half+4-(size_half%4)])
        encrypted = bytes(x^^y for x,y in zip(second, sha1_result)) + first
    return encrypted + last

# unpack&decrypt installationId
iid = int(installationId).to_bytes(installationIdSize, byteorder='little')
iid = decrypt(iid, b'\x6A\xC8\x5E\xD4')
hwid = iid[:8]
productid = int.from_bytes(iid[8:17], byteorder='little')
productkeyhash = iid[17:]
pid1 = productid & ((1 << 17) - 1)
pid2 = (productid >> 17) & ((1 << 10) - 1)
pid3 = (productid >> 27) & ((1 << 25) - 1)
version = (productid >> 52) & 7
pid4 = productid >> 55

assert version == (4 if len(iid) == 17 else 5)

key = hwid + int((pid1 << 41 | pid2 << 58 | pid3 << 17 | pid4) & ((1 << 64) - 1)).to_bytes(8, byteorder='little')
# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids

# now the math

p = 0x16A6B036D7F2A79
Fp = GF(p)
Fpx.<x> = Fp[]
E = HyperellipticCurve(x^5+0x1400606322B3B04*x^4+0x1400606322B3B04*x^3+0x44197B83892AD0*x^2+0x21840136C85381*x)
J = E.jacobian()

# deserialize divisor
x1 = confirmationId // (p + 1)
x2 = confirmationId % (p + 1)
if x1 <= p:
    # two or less points over GF(p)
    point1 = E.lift_x(Fp(-x1)) if x1 != p else None
    point2 = E.lift_x(Fp(-x2)) if x2 != p else None
    if point1 is not None and point2 is not None:
        # there are 4 variants of how lift_x() could select both y-s
        # we don't distinguish D and -D, but this still leaves 2 variants
        # the chosen one is encoded by order of x1 <=> x2
        lastbit1 = point1[1].lift() & 1
        lastbit2 = point2[1].lift() & 1
        if x2 < x1:
            if lastbit1 == lastbit2:
                point2 = E(point2[0], -point2[1])
        else:
            if lastbit1 != lastbit2:
                point2 = E(point2[0], -point2[1])
    point1 = J(point1) if point1 is not None else J(0)
    point2 = J(point2) if point2 is not None else J(0)
    divisor = point1 + point2
else:
    # a pair of conjugate points over GF(p^2)
    f = (x+x2)*(x+x2)-43*x1*x1 # 43 is the minimal quadratic non-residue in Fp
    Fp2 = GF(p^2)
    point1 = E.lift_x(f.roots(Fp2)[0][0])
    point2 = E(Fp2)(point1[0].conjugate(), point1[1].conjugate())
    divisor = J(Fp2)(point1) + J(Fp2)(point2)
    divisor = J(Fpx(divisor[0]), Fpx(divisor[1])) #return from Fp2 to Fp

d2 = divisor * 0x10001
assert d2[0].degree() == 2
x1 = d2[0][1]/2
x2 = sqrt((x1*x1-d2[0][0])/43)

encrypted = x1.lift() + (x2.lift() - 1) * p
encrypted = int(encrypted).to_bytes(14,byteorder='little')

# end of the math
decrypted = decrypt(encrypted, key)
print(decrypted.hex())
# 0000000000000001000000000000 for the first confirmationId
# 0000000000000002000000000000 for the second confirmationId
# 0000000000000006000000000000 for the last confirmationId
assert decrypted[8:] == b'\0' * 6
assert decrypted[7] <= 0x80
# all zeroes in decrypted[0:7] are okay for the checker
# more precisely: if decrypted[6] == 0, first 6 bytes can be anything
# otherwise, decrypted[0] = length, and decrypted[1:1+length] must match first length bytes of sha1(product key)