Last active
November 24, 2023 06:43
-
-
Save WitherOrNot/7ac1be87811df327d4025a2c2ad5196c to your computer and use it in GitHub Desktop.
Office 2K3/2K7 confirmation ID generation (by david4599)
This file contains 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
import hashlib | |
def add_pid_cksum(pid): | |
sumPID = 0 | |
val = pid | |
while val != 0: | |
sumPID += val % 10 | |
val //= 10 | |
return pid * 10 + 7 - (sumPID % 7) | |
def decode_iid_new_version(iid, pid): | |
buffer = [0] * 5 | |
for i in range(len(buffer)): | |
buffer[i] = int.from_bytes(iid[i*4:i*4+4], byteorder='little') | |
# print("buffer[" + str(i) + "] = " + hex(buffer[i])[2:].zfill(8)) | |
v1 = (buffer[3] & 0xFFFFFFF8) | 2 # Not really sure but seems to work | |
v2 = (buffer[3] & 7) << 29 | buffer[2] >> 3 | |
hardwareId = (v1) << 32 | v2 | |
hardwareId = int(hardwareId).to_bytes(8, byteorder='little') | |
v3 = (buffer[0] & 0xFFFFFF80) >> 7 & 0xFFFFFFFF | |
unknown1 = v3 & 0x000007FF | |
v4 = v3 & 0xFFFFF800 | |
v5 = buffer[1] & 0x7F | |
v6 = buffer[1] >> 7 | |
v7 = (v5 << 25 | v4) >> 11 | |
productID1 = v7 & 0x000003FF | |
v8 = v7 & 0xFFFFFC00 | |
v9 = (v6 >> 11) & 0x00001FFF | |
v10 = v9 & 0x00001C00 | |
v11 = v9 & 0x000003FF | |
v12 = ((v6 << 21) & 0xFFFFFFFF | v8) >> 10 | |
v13 = (v11 << 22) & 0xFFFFFFFF | |
v14 = v13 | v12 | |
productID3RandomPart = (v14 & 0x3FF00000) >> 20 | |
productID2NoChecksum = v14 & 0x000FFFFF | |
v15 = v13 >> 30 # 0x00000003 | |
v16 = v10 >> 8 # 0x0000001C | |
v17 = (buffer[2] & 7) << 6 # 0x000001C0 | |
v18 = (buffer[4] & 1) << 9 # 0x00000200 | |
authInfo = v18 | v17 | v16 | v15 # Not that important bug: bit 5 is not present | |
productID0 = pid[0] | |
productID2 = add_pid_cksum(productID2NoChecksum) | |
productID3 = (pid[3] // 1000) * 1000 + productID3RandomPart | |
# Just to remember: public key index I of pid (XXXXX-XXX-XXXXXXX-IIXXX) = BINK ID // 2 | |
# Where is actually located the version number? | |
# version1 = buffer[0] & 7 | |
# print("Decoded IID Version1?: " + str(version1)) | |
# version2 = (int.from_bytes(iid[8:17], byteorder='little') >> 52) & 7 | |
# print("Decoded IID Version2?: " + str(version2)) | |
# version3 = buffer[3] & 7 | |
# print("Decoded IID Version3?: " + str(version3)) | |
if productID1 != pid[1] or productID2 != pid[2] or pid[3] % 1000 != productID3RandomPart: | |
print("Error: Product ID not matching!") | |
return 0, 0, 0 | |
return hardwareId, authInfo, unknown1 | |
# 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:] | |
# A magic byte 0x79 is now added at the beginning of the list of bytes to hash | |
sha1_result = hashlib.sha1(bytearray.fromhex("79") + 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:] | |
# A magic byte 0x79 is now added at the beginning of the list of bytes to hash | |
sha1_result = hashlib.sha1(bytearray.fromhex("79") + 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 | |
# 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' | |
#""" | |
# 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() | |
# unpack&decrypt installationId | |
installationId = validate_cksum(input("Installation ID (dashes optional): ")) | |
productId = input("Product ID (with dashes): ").split("-") | |
pid = [int(x) for x in productId] | |
# Office 2003 Professional Edition FWYTB-C7PPP-4497G-FV737-2HQWG (UMSKT generated) | |
# installationId = 020572391118023984229275432949036355811509788 # 020570-239116-180233-984220-927546-329495-036352-581151-097880 | |
# pid = [73931, 746, 6952006, 57345] # 73931-746-6952006-57345 | |
# Office 2007 Enterprise Edition XGQ68-R77XM-FPYFH-B436K-46QDY (UMSKT generated) | |
# installationId = 032422660398632786377841998280144793681167281 # 032424-266032-986324-786370-784193-982801-144791-368115-672814 | |
# pid = [89388, 864, 6523093, 65443] # 89388-864-6523093-65443 | |
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, authInfo, unknown1 = decode_iid_new_version(iid, pid) | |
print("\nDecoded Hardware ID: " + hex(int.from_bytes(hwid, byteorder='big'))) | |
print("Decoded AuthInfo: " + hex(authInfo)) | |
print("Decoded Unknown1: " + hex(unknown1)) | |
assert hwid != 0 | |
key = hwid + int((pid[0] << 41 | pid[1] << 58 | pid[2] << 17 | pid[3]) & ((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[6] = i # Attempt number was byte 7 in older confirmation ID version but it is now byte 6 | |
# 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: | |
print(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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment