Files
WindowsXPKg/extras/confid.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

16 KiB

Constants (run this cell first!)

In [ ]:
"""
# MS Plus! DME

# order of field Fp 
p = 0x16A5DABA0605983
# Coefficients of F
coeffs = [0x334f24f75caa0e, 0x1392ff62889bd7b, 0x135131863ba2db8, 0x153208e78006010, 0x163694f26056db, 1]
# This constant inverts multiplication by 0x10001 in verification
INV = 0x01c61212ece6107c4254c43a5d1181
# Key to decrypt installation IDs
IID_KEY = b'\x6A\xC8\x5E\xD4'
#"""

#"""
# Office XP/2003

# order of field Fp 
p = 0x16E48DD18451FE9
# Coefficients of F
coeffs = [0, 0xE5F5ECD95C8FD2, 0xFF28276F11F61, 0xFB2BD9132627E6, 0xE5F5ECD95C8FD2, 1]
# This constant inverts multiplication by 0x10001 in verification
INV = 0x01fb8cf48a70dfefe0302a1f7a5341
# Key to decrypt installation IDs
IID_KEY = b'\x5A\x30\xB9\xF3'
#"""

"""
# Whistler 2428 (could be others)

# order of field Fp 
p = 0x16BD82821354FA3
# Coefficients of F
coeffs = [0, 0xDEFD8C5651954F, 0xA23AA12556ECE5, 0x89D79AD61B786D, 0xCCA087F0A6A4A4, 1]
# This constant inverts multiplication by 0x10001 in verification
INV = 0xd9ed873ed84a45761c23fd7fafd1
# Key to decrypt installation IDs
IID_KEY = b'\x6A\xC8\x5E\xD4'
#"""


"""
# Windows XP/Server 2003/Longhorn Pre-Reset

# order of field Fp 
p = 0x16A6B036D7F2A79
# Coefficients of F
coeffs = [0, 0x21840136C85381, 0x44197B83892AD0, 0x1400606322B3B04, 0x1400606322B3B04, 1]
# This constant inverts multiplication by 0x10001 in verification
INV = 0x40DA7C36D44C04E21B9D10F127C1
# Key to decrypt installation IDs
IID_KEY = b'\x6A\xC8\x5E\xD4'
#"""

# minimal quadratic non-residue of p
mqnr = least_quadratic_nonresidue(p)
# Galois field of order p
Fp = GF(p)
# Polynomial field Fp[x] over Fp
Fpx.<x> = Fp[]

# Hyperellptic curve function
F = sum(k*x^i for i, k in enumerate(coeffs))
# Hyperelliptic curve E: y^2 = F(x) over Fp
E = HyperellipticCurve(F)
# The jacobian over E
J = E.jacobian()

Generate Confirmation ID

In [ ]:
import hashlib

# 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(n_out)

# 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): "))
# installationId = 11234597509478704096883784033789146715149
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 << 24) - 1)
version = (productid >> 52) & 7
pid4 = productid >> 55

if version != (4 if len(iid) == 17 else 5):
    print(f"Invalid Installation ID (unknown version {version})")

print(installationIdSize)
print(pid1, pid2, pid3, pid4)

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

data = [0x00] * 14
# data = b'\xb9g\xdd\xe1\xb0\xef-\x1e\xbd\x0frE\xd8\xbe'
print("\nConfirmation IDs:")

for i in range(0x81):
    data[4] = 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) * mqnr)
    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]) / mqnr).lift() + p

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

Validate Confirmation ID (originally by diamondggg)

In [ ]:
import hashlib

# 226512-274743-842923-777124-961370-722240-570042-517722-757426
installationId = 44706039542602728435285810860722693781
installationIdSize = 17 # 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
confid = input("Confirmation ID: ").replace("-", "")
confirmationId = int("".join([confid[i:i+5] for i in range(0, len(confid), 6)]))
# confirmationId = 13009861034010972507754924748629391
print(confirmationId)
# 022032-220754-159721-909624-985141-504586-914001
#confirmationId = 2203220751597290962985145045891400
# 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, IID_KEY)
hwid = iid[:8]
productid = int.from_bytes(iid[8:17], byteorder='little')
# productkeyhash is not used for validation, it exists just to allow the activation server to reject keygenned pids
productkeyhash = iid[17:]
pid1 = productid & ((1 << 17) - 1)
pid2 = (productid >> 17) & ((1 << 10) - 1)
pid3 = (productid >> 27) & ((1 << 24) - 1)
version = (productid >> 52) & 7
pid4 = productid >> 55

print(pid1, pid2, pid3, pid4)

if version != (4 if len(iid) == 17 else 5):
    print(version)

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

# 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) - mqnr*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])/mqnr)

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)
In [ ]: