Skip to content

Instantly share code, notes, and snippets.

@DonnchaC
Created September 24, 2016 19:27
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save DonnchaC/09c9de3a73b0fd29c699d4f3ce038074 to your computer and use it in GitHub Desktop.
Save DonnchaC/09c9de3a73b0fd29c699d4f3ce038074 to your computer and use it in GitHub Desktop.
Huaiwei unlock code generator - Based on the disassembler generated C code in https://github.com/forth32/huaweicalc
#!/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()
@vitasgul
Copy link

vitasgul commented May 6, 2017

not work in python 3

@felicianotech
Copy link

What happens for a WiFi only device which doesn't have an IMEI?

@fogside
Copy link

fogside commented Jan 17, 2020

it seems that current unlock code length is 16, not 8 as given here.

@Logicbloke
Copy link

@fogside

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.

@leblanc1810
Copy link

@fogside

il semble que la longueur actuelle du code de déverrouillage soit de 16, et non de 8 comme indiqué ici.

Je crois que le code actuel est connu sous le nom de v5 et est un code alphanumérique de 16 caractères.

yes

@SheilaBirgen
Copy link

@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.

@SheilaBirgen
Copy link

@fogside

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.

@Logicbloke
Copy link

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment