-
-
Save DonnchaC/09c9de3a73b0fd29c699d4f3ce038074 to your computer and use it in GitHub Desktop.
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
import os | |
import hashlib | |
import argparse | |
import binascii | |
import struct | |
def encrypt_v1(imei, key): | |
""" | |
The V1 unlock system | |
This system uses hardcoded keys. | |
""" | |
salt = hashlib.md5(key).hexdigest()[8:24] | |
digest = hashlib.md5((imei + salt).lower()).digest() | |
code = 0 | |
for i in range(0, 4): | |
code += (ord(digest[i]) ^ ord(digest[4 + i]) ^ | |
ord(digest[8 + i]) ^ ord(digest[12 + i])) << (3 - i) * 8 | |
return str((code & 0x1ffffff) | 0x2000000) | |
def encrypt_v2_1(imei, version): | |
# Magic bytes from somewhere | |
key_2 = [ | |
0x01966A9, 0x021058F, 0x02AEDA9, 0x037CE91, 0x0488C9F, 0x05E507D, | |
0x07A9BE5, 0x09F644B, 0x0CF35A1, 0x10D5F55, 0x15E2F25, 0x1C73D6B, | |
0x24FCFDD, 0x3015B47, 0x3E829E9, 0x5143685 | |
] | |
key_201 = [ | |
0x06E9C2A, 0x3CA2B3C, 0x01080DC, 0x30855EE, 0x3D3283A, 0x2F4F85A, | |
0x1F8808E, 0x3147D10, 0x34BBBB5, 0x29EEADD, 0x2318616, 0x50F3ADC, | |
0x0D11F38, 0x2123BD2, 0x4276C86, 0x355CAAD | |
] | |
if version == 201: | |
magic_bytes = key_201 | |
else: | |
magic_bytes = key_2 | |
csum = 0 | |
for i, digit in enumerate(imei): | |
csum += ((ord(digit) * magic_bytes[i])) | |
# Truncate to an unsigned long | |
csum &= 0xffffffff | |
# Extract bit integers from the checksum, get mod 10 | |
zvar = [] | |
for i in range(8): | |
zvar.append(((csum & (0xf << (i * 4))) >> (i * 4)) % 10) | |
# Add 1 to not have leading zero | |
if zvar[0] == 0: | |
zvar[0] = 1 | |
# Join the array of integers | |
return ''.join([str(i) for i in zvar]) | |
def encrypt_v2_2(imei, version): | |
"""This algorithim is two CRC32 implementation.""" | |
def custom_crc32(imei): | |
""" | |
Non standard CRC32 used in v201 and v3 | |
""" | |
crc_table_v201 = [ | |
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x76DC419, | |
0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, | |
0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, | |
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, | |
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, | |
0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, | |
0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, | |
0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, | |
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, | |
0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, | |
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, | |
0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, | |
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, | |
0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, | |
0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, | |
0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, | |
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, | |
0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, | |
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, | |
0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, | |
0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, | |
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, | |
0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, | |
0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, | |
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, | |
0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, | |
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, | |
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, | |
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, | |
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, | |
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, | |
0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, | |
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, | |
0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, | |
0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, | |
0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, | |
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, | |
0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, | |
0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, | |
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, | |
0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, | |
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, | |
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, | |
0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, | |
0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, | |
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, | |
0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, | |
0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, | |
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, | |
0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, | |
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, | |
0x2D02EF8D, | |
] | |
csum = 0xffffffff | |
for i, digit in enumerate(imei): | |
csum = crc_table_v201[(csum & 0xff) ^ ord(digit)] ^ (csum >> 8) | |
# Truncate to unsigned int | |
csum &= 0xffffffff | |
return csum | |
if version == 201: | |
# Version 201/3 uses a custom CRC32 block | |
crc32 = custom_crc32(imei) | |
# Represent unsigned crc32 as signed int | |
crc32 = struct.unpack('>i', struct.pack('>I', ~crc32 & 0xffffffff))[0] | |
crc32 = abs(crc32) | |
else: | |
# Version 2 uses a standard CRC32 block | |
crc32 = abs(binascii.crc32(imei)) & 0xffffffff | |
if crc32 == 0: | |
return '99999999' | |
else: | |
# Reverse the crc32 number, and pad on left with '9's | |
result = list(str(crc32)[-8:]) | |
# Replace a leading zero with nine | |
if result[0] == '0': | |
result[0] = '9' | |
# Join result and pad left with 9's | |
return ''.join(result).rjust(8, '9') | |
def encrypt_v2_3(imei, version): | |
""" | |
MD5 digest algorithim | |
""" | |
digest = hashlib.md5(imei).digest() | |
if version == 201: | |
digest_bytes = list(digest[5:5+8]) | |
else: | |
digest_bytes = list(digest[0:8]) | |
# Replace first digit if it begins with zero | |
first_digit = ord(digest_bytes[0]) % 10 | |
if (first_digit) == 0: | |
digest_bytes[0] = '5' | |
else: | |
digest_bytes[0] = str(first_digit) | |
# Use suitable digits or base 10 bytes to get a single decimal digit | |
result = [] | |
for byte in digest_bytes: | |
# Byte is already a single digit character, don't mod | |
if (byte >= '0') and (byte <= '9'): | |
result.append(byte) | |
else: | |
result.append(ord(byte) % 10) | |
return ''.join([str(i) for i in result]) | |
def encrypt_v2_4(imei, version): | |
""" | |
MD5 with version specific salt | |
""" | |
def md5_hash(imei, key): | |
salt = hashlib.md5(key).digest() | |
return hashlib.md5(imei + salt).digest() | |
if version == 201: | |
digest = md5_hash(imei, key="dfkdkfllekkodk") | |
else: | |
digest = md5_hash(imei, key="hwideadatacard") | |
code = 0 | |
for i in range(0, 4): | |
digit = ((ord(digest[i]) ^ ord(digest[i+4]) ^ | |
ord(digest[i+8]) ^ ord(digest[i+12]))) | |
code = (code << 8) | (digit & 0xff) | |
return str((code & 0x1ffffff) | 0x2000000) | |
def encrypt_v2_5(imei, version): | |
""" | |
Substitution cipher based on IMEI | |
""" | |
pw_table = "5739146280098765432112345678905\000" | |
result = [] | |
imei_str = imei + 'Z' | |
for i in range(0, 8): | |
digit = ((ord(imei_str[i]) ^ ord(imei_str[i+8])) & 0xff) | |
result.append(int(pw_table[(digit >> 4) + (digit & 0x0f)])) | |
# Dont start with zero, set first digit to the offset of the first | |
# non-zero digit. | |
if result[0] == 0: | |
for i, digit in enumerate(result): | |
if digit != 0: | |
break | |
result[0] = i | |
return ''.join([str(i) for i in result]) | |
def encrypt_v2_6(imei, version): | |
""" | |
SHA1 digest of IMEI | |
""" | |
digest = hashlib.sha1(imei).digest() | |
# Chunk hash as unsigned integers, unpack four bytes as an unsigned int | |
int_array = [] | |
for i in range(0, len(digest), 4): | |
int_array.append(str(struct.unpack(">I", digest[i:i+4])[0])) | |
if version == 2: | |
result = int_array[0] + int_array[1] | |
elif version == 5: | |
result = int_array[1] + int_array[4] | |
elif version == 6: | |
result = int_array[2] + int_array[3] | |
# Pad the result with zeros to make 8 digit code | |
return result[0:8].ljust(8, '0') | |
def encrypt_v2_7(imei, version): | |
""" | |
Keyed cipher and MD5 digest | |
""" | |
cb_2 = [ | |
0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0D, 0x15, 0x22, 0x37, 0x59, 0x90 | |
] | |
cb_201 = [ | |
0x0B, 0x0D, 0x11, 0x13, 0x17, 0x1D, 0x1F, 0x25, 0x29, 0x2B, 0x3B, 0x61 | |
] | |
if version == 201: | |
key = cb_201 | |
else: | |
key = cb_2 | |
result = [] | |
for i, digit in enumerate(imei): | |
digit = ord(digit) | |
if (i % 3) == 0: | |
result.append(((digit << 6) | (digit >> 2)) & 0xff) | |
elif (i % 3) == 1: | |
result.append(((digit << 5) | (digit >> 3)) & 0xff) | |
else: | |
result.append(((digit >> 4) | (digit << 4)) & 0xff) | |
hsum = 0 | |
for i in range(0, 7): | |
hsum += result[14-i] + (result[i] << 8) | |
hsum += result[8] | |
# Pad buffer with 0's | |
buf128 = result + ([0] * (128 - len(result))) | |
# TODO: Understand what this chunk of code does: | |
# Appears to do divison by 6. | |
r8 = 0 | |
for i in range(15, 0x80): | |
r6 = i | |
r3 = i >> 31 | |
lr = 0x2AAAAAAB | |
cx = 0x2AAAAAAB * i | |
r1 = cx >> 32 | |
cx = lr * r8 | |
lr = cx >> 32 | |
r0 = r8 >> 31 | |
r2 = r0 | |
r5 = (r1 >> 1) - r3 | |
r12 = r5 << 4 | |
r0 = (lr >> 1) - r0 | |
r2 = (lr >> 1) - r2 | |
r1 = r0 << 4 | |
r12 = r12 - (r5 << 2) | |
r3 = r2 << 4 | |
lr = r6 - r12 | |
r1 = r1 - (r0 << 2) | |
r7 = r5 + lr | |
r3 = r3 - (r2 << 2) | |
r1 = r8 - r1 | |
r2 = r5 + r1 | |
r3 = r8 - r3 | |
r12 = r12 - 0x18 | |
if r7 > 0xb: | |
r7 = r7 - 0xc | |
r3 += r5 | |
if r5 > 1: | |
r3 = r2 + r12 | |
r0 = hsum | |
r1 = r6 | |
if r8 == 0: | |
r4 = buf128[r3] | |
r0 = r0 % r1 | |
r1 = key[r7] | |
r4 = r4 & r1 | |
r12 = buf128[r0] | |
r3 = buf128[r0+1] | |
r4 |= r12 | |
else: | |
r1 = r6 | |
r0 = hsum | |
r4 = buf128[r3] | |
r0 = r0 % r1 | |
r1 = r8 | |
r5 = buf128[r0] | |
r0 = hsum | |
r0 = r0 % r1 | |
r3 = buf128[r0] | |
r2 = key[r7] | |
r4 = r4 & r2 | |
r4 = r4 | r5 | |
r3 = ~r3 | |
r3 = r3 | r4 | |
r3 &= 0xff | |
buf128[i] = r3 | |
r8 += 1 | |
byte_array = ''.join([chr(b) for b in buf128]) | |
csum = 0 | |
for i in range(0, 7): | |
csum += (ord(imei[i+1]) | (ord(imei[i]) << 8)) | |
csum += ord(imei[14]) | |
digest = hashlib.md5(byte_array).digest() | |
# Pick bytes from the digest which are integers | |
result = [] | |
for byte in digest: | |
if (byte >= '0') and (byte <= '9'): | |
result.append(byte) | |
if len(result) > 7: | |
break | |
def int_from_bytestream(byte_stream): | |
"""Convert a 4 byte chunk to an integer""" | |
return struct.unpack("<I", byte_stream[0:4])[0] | |
# Extract an integer from the hash | |
offset = (csum & 3) << 2 | |
extra_num = str(int_from_bytestream(digest[offset:])) | |
# Cycle 1 | |
if len(result) < 8: | |
# Don't have enough numbers, read more digits from the end | |
# of extra_number until we have 8 digits. | |
while len(result) < 8: | |
extra_num, last_digit = extra_num[:-1], extra_num[-1] | |
result.append(last_digit) | |
# If still nor enough digits, pick a new integer from digest | |
if not extra_num: | |
offset = (3 - (csum & 3)) << 2 | |
extra_num = str(int_from_bytestream(digest[offset:])) | |
# Replace any leading zeros | |
if result[0] == '0': | |
if csum != 0: | |
offset = 1 | |
else: | |
offset = 0 | |
# Add one to digit to ensure non zero | |
result[0] = str((ord(digest[offset]) & 7) + 1) | |
return ''.join([str(i) for i in result]) | |
def proc_index(imei, version): | |
""" | |
Determine `index` for the IMEI | |
The index determines which of the 7 algorithims should be usedbytes | |
for the unlock code generation. | |
""" | |
csum = 0 | |
for i, digit in enumerate(imei, 1): | |
ch = ord(digit) | |
if version == 201: | |
csum += (ch + i) * ch * (ch + 313) | |
else: | |
csum += (ch+i) * i | |
cx = (-0x6db6db6d * csum) >> 32 | |
c1 = ((cx + csum) >> 2) - (csum >> 31) | |
return csum - ((c1 << 3) - c1) | |
def calc_2(imei, version): | |
""" | |
Select the correct crypto algorithim based on the IMEI | |
""" | |
# Algorithim set for v2 | |
encryption_algo_v2 = { | |
0: (encrypt_v2_1, 2), | |
1: (encrypt_v2_2, 2), | |
2: (encrypt_v2_3, 2), | |
3: (encrypt_v2_4, 2), | |
4: (encrypt_v2_5, None), | |
5: (encrypt_v2_6, 2), | |
6: (encrypt_v2_7, 2), | |
} | |
# Algorithim set for v201 / v3 | |
encryption_algo_v201 = { | |
0: (encrypt_v2_1, 201), | |
1: (encrypt_v2_2, 201), | |
2: (encrypt_v2_3, 201), | |
3: (encrypt_v2_4, 201), | |
4: (encrypt_v2_6, 5), | |
5: (encrypt_v2_6, 6), | |
6: (encrypt_v2_7, 201), | |
} | |
index = proc_index(imei, version) | |
if version == 2: | |
algorithim, algo_version = encryption_algo_v2[index] | |
elif version == 201 or version == 3: | |
algorithim, algo_version = encryption_algo_v201[index] | |
return algorithim(imei, algo_version) | |
def unlock(imei, version): | |
""" | |
Public unlock function | |
Choose the correct unlock algorithim based on the version | |
""" | |
if version == 1: | |
return encrypt_v1(imei, 'hwe620datacard') | |
elif version == 2: | |
# Version v2 | |
return calc_2(imei, 2) | |
elif version == 201 or version == 3: | |
# Version v201/v3 | |
return calc_2(imei, 201) | |
elif version == 'flash': | |
return encrypt_v1(imei, 'e630upgrade') | |
def run_tests(): | |
""" | |
Run tests | |
""" | |
# These are test case which check some tricky cases. | |
assert(encrypt_v2_1('166794546749343', 201) == '31572464') | |
assert(encrypt_v2_2('867010022091625', 2) == '89740701') | |
assert(encrypt_v2_2('867010022093346', 2) == '90496577') | |
assert(encrypt_v2_2('867010022091336', 201) == '43479313') | |
assert(encrypt_v2_2('486043736169958', 201) == '20766653') | |
assert(encrypt_v2_2('152782107774300', 201) == '99353390') | |
assert(encrypt_v2_3('867010022091626', 2) == '55760904') | |
assert(encrypt_v2_3('867010022091545', 2) == '77395563') | |
assert(encrypt_v2_3('867010022091566', 201) == '98820346') | |
assert(encrypt_v2_3('133887909865624', 201) == '13553393') | |
assert(encrypt_v2_4('867010022091677', 2) == '50284150') | |
assert(encrypt_v2_4('867010022091677', 201) == '48425064') | |
assert(encrypt_v2_5('867010022091661', 2) == '16672676') | |
assert(encrypt_v2_5('867010022091698', 2) == '16672086') | |
assert(encrypt_v2_6('867010022091692', 2) == '16678430') | |
assert(encrypt_v2_6('867010022091696', 5) == '26958384') | |
assert(encrypt_v2_6('867010022091697', 6) == '11406485') | |
assert(encrypt_v2_7('867010022093344', 2) == '41232318') | |
assert(encrypt_v2_7('234242342432305', 2) == '68014899') | |
assert(encrypt_v2_7('221724677371250', 2) == '92023179') | |
assert(encrypt_v2_7('867010022093350', 201) == '13122759') | |
assert(proc_index('667010022091624', 201) == 2) | |
assert(proc_index('867010022091624', 201) == 3) | |
assert(proc_index('867010022091624', 2) == 0) | |
assert(encrypt_v2_7('221724677371250', 2) == '92023179') | |
# Try load extra test cases from file | |
failed = False | |
for test_type in [1, 2, 3]: | |
try: | |
test_file = os.path.join("tests", "test-{}.txt".format(test_type)) | |
for test_line in open(test_file, 'r'): | |
imei, expected = test_line.strip().split(' ') | |
calculated = unlock(imei, test_type) | |
if calculated != expected: | |
print("Error: IMEI: %s Calculated: %s Expected: %s" % | |
(imei, calculated, expected)) | |
failed = True | |
except OSError: | |
print("Could not open test cases in '{}'.".format(test_file)) | |
if failed: | |
print("Tests failed") | |
else: | |
print("All tests passed!") | |
def main(): | |
parser = argparse.ArgumentParser( | |
description="Generate Huawei device unlock codes.") | |
parser.add_argument("imei", type=str, help="The device IMEI number") | |
parser.add_argument('--test', action="store_true", | |
help="Run the test cases") | |
args = parser.parse_args() | |
if args.test: | |
return run_tests() | |
if len(args.imei) != 15 or not args.imei.isdigit(): | |
print("Not a valid IMEI") | |
else: | |
# Looks like a valid IMEI, calculate codes. | |
imei = args.imei | |
print('IMEI: {}'.format(imei)) | |
print('Unlock (V1): {}'.format(unlock(imei, 1))) | |
print('Unlock (V2): {}'.format(unlock(imei, 2))) | |
print('Unlock (V3/201): {}'.format(unlock(imei, 3))) | |
print('Flash: {}'.format(unlock(imei, 'flash'))) | |
if __name__ == '__main__': | |
main() |
What happens for a WiFi only device which doesn't have an IMEI?
it seems that current unlock code length is 16, not 8 as given here.
I believe the current code is known as v5 and is a 16-char alphanumeric code.
@fogside did you get one that gives a 16 digits unlock code?
it seems that current unlock code length is 16, not 8 as given here.
it seems that current unlock code length is 16, not 8 as given here.
I believe the current code is known as v5 and is a 16-char alphanumeric code.
Can you kindly direct me to where I can get v5 unlock code calculator please.
@SheilaBirgen I was looking for it as well when I landed here. Haven't found it since but if someone finds it, please link to it from here. Thanks!
@SheilaBirgen I was looking for it as well when I landed here. Haven't found it since but if someone finds it, please link to it from here. Thanks!
Have you found any?
@shujarafi The best solution is probably to reflash the firmware by connecting to the UART pins of the router.
it seems that current unlock code length is 16, not 8 as given here.
I believe the current code is known as v5 and is a 16-char alphanumeric code.
Hello , did you found any tool or something to generate v5 16-char alphanumeric unlock code ?
thank you
@SheilaBirgen I was looking for it as well when I landed here. Haven't found it since but if someone finds it, please link to it from here. Thanks!
Hello , did you found any tools ?
Thanks
not work in python 3