天堂通訊加密解析

天堂加解密代碼(Python版)

# -*- coding: utf-8 -*-
from binascii import hexlify, unhexlify

def long_to_bytes(val, endianness='big'):
    width = val.bit_length()
    width += 8 - ((width % 8) or 8)
    fmt = '%%0%dx' % (width // 4)
    s = unhexlify(fmt % val)
    if endianness == 'little':
        s = s[::-1]
    return s

def bytes_to_long(val, endianness='big'):
    s = val[:4]
    if endianness == 'little':
        s = s[::-1]
    return int(hexlify(s), 16)

def dumpToString(byteArray):
    j = 0
    str = ''
    for k in range(len(byteArray)):
        # 打印起始字節號
        if j % 16 == 0:
            str += '{:0>4x}'.format(k) + ': '
        # 十六進制打印bytes數據
        str += '{:0>2x}'.format(byteArray[k] & 0xFF) + ' '
        j += 1
        if j != 16:
            continue

        # ascii打印bytes數據
        str += '   '
        i = k - 15
        for l in range(16):
            byte0 = byteArray[i]
            i += 1
            if byte0 > 31 and byte0 < 128:
                str += chr(byte0)
            else:
                str += '.'
            str += '\n'
            j = 0

        # 若是最後一次不足16須要單獨處理
        l = len(byteArray) % 16
        if l > 0:
            # 用空格代替十六進制填充不足的bytes數據
            for j in range(17 - l):
                str += '    '
            # ascii打印剩餘字節
            k = len(byteArray) - l
            for i in range(l):
                byte0 = byteArray[k]
                k += 1
                if byte0 > 31 and byte0 < 128:
                    str += chr(byte0)
                else:
                    str += '.'
            str += '\n'
    print(str)

def decrypt(dk, buf):

    b3 = buf[3]
    buf[3] ^= dk[2]

    b2 = buf[2]
    buf[2] ^= (b3 ^ dk[3])

    b1 = buf[1]
    buf[1] ^= (b2 ^ dk[4])

    k = buf[0] ^ b1 ^ dk[5]
    buf[0] = k ^ dk[0]

    i = 1
    while i < len(buf):
        t = buf[i]
        buf[i] ^= (dk[i & 7] ^ k)
        k = t
        i = i + 1

    return buf


def encrypt(ek, buf):

    buf[0] ^= ek[0]
    i = 1
    while i < len(buf):
        buf[i] ^= (buf[i - 1] ^ ek[i & 7])
        i = i + 1

    buf[3] = buf[3] ^ ek[2]
    buf[2] = buf[2] ^ buf[3] ^ ek[3]
    buf[1] = buf[1] ^ buf[2] ^ ek[4]
    buf[0] = buf[0] ^ buf[1] ^ ek[5]

    return buf


if __name__ == '__main__':
    '''
    ### 這邊是從java版本的天堂開發環境提取出的實際數據
    [initKeys]
    0x316a030b, 0x930fd7e2
    [getSeeds]
    0x316a030b, 0x930fd7e2
    [after getSeeds]
    0xb1956ad6, 0x5ee854a7
    [before decrypt]
    0000: 97 23 3d a0 91 c5 2d 73 71 ab 3f 8e                .#=...-sq.?.
    [key]
    0xb1956ad6, 0x5ee854a7
    [after decrypt]
    0000: 36 33 00 a8 03 00 00 00 d4 b0 01 00                63..........
    [key]
    0xb1956ad6, 0x5ee854a7
    [mask & key]
    0xa8003336, 0x199559e0, 0x8767546a
    '''

    # 一開始客戶端和服務端的全部密鑰都是同樣的
    srv_decrypt_key = bytearray(long_to_bytes(0xb1956ad6, 'little') + long_to_bytes(0x5ee854a7, 'little'))
    # srv_encrypt_key = srv_decrypt_key
    # cli_decrypt_key = srv_decrypt_key
    cli_encrypt_key = srv_decrypt_key

    raw_data = bytearray([0x36, 0x33, 0x00, 0xa8, 0x03, 0x00, 0x00, 0x00, 0xd4, 0xb0, 0x01, 0x00])
    print("Raw data:")
    dumpToString(raw_data)

    # 客戶端往服務端發送數據
    # 客戶端處理
    mask = bytes_to_long(raw_data, 'little')
    encrypt_data = encrypt(cli_encrypt_key, raw_data)
    cli_encrypt_key = bytearray(
        long_to_bytes(bytes_to_long(cli_encrypt_key[:2], 'little') ^ mask, 'little') + long_to_bytes(
            bytes_to_long(cli_encrypt_key[4:], 'little') + 0x287EFFC3, 'little'))
    print("Client send data:")
    dumpToString(encrypt_data)
    # 服務端處理
    plain_data = decrypt(srv_decrypt_key, encrypt_data)
    mask = bytes_to_long(plain_data, 'little')
    srv_decrypt_key = bytearray(
        long_to_bytes(bytes_to_long(srv_decrypt_key[:2], 'little') ^ mask, 'little') + long_to_bytes(
            bytes_to_long(srv_decrypt_key[4:], 'little') + 0x287EFFC3, 'little'))
    print("Server receives data:")
    dumpToString(plain_data)

    # 客戶端再次往服務端發送數據(使用更新後的密鑰)
    # 客戶端處理
    mask = bytes_to_long(raw_data, 'little')
    encrypt_data = encrypt(cli_encrypt_key, raw_data)
    cli_encrypt_key = bytearray(
        long_to_bytes(bytes_to_long(cli_encrypt_key[:2], 'little') ^ mask, 'little') + long_to_bytes(
            bytes_to_long(cli_encrypt_key[4:], 'little') + 0x287EFFC3, 'little'))
    print("Client send data again:")
    dumpToString(encrypt_data)
    # 服務端處理
    plain_data = decrypt(srv_decrypt_key, encrypt_data)
    mask = bytes_to_long(plain_data, 'little')
    srv_decrypt_key = bytearray(
        long_to_bytes(bytes_to_long(srv_decrypt_key[:2], 'little') ^ mask, 'little') + long_to_bytes(
            bytes_to_long(srv_decrypt_key[4:], 'little') + 0x287EFFC3, 'little'))
    print("Server receives data again:")
    dumpToString(plain_data)

分析

從上面能夠看出,decrypt函數和encrypt函數互爲逆操做,說明只要加密和解密的密鑰相同,那麼就可以還原數據,那麼問題就變成了,客戶端和服務端是如何保證密鑰相同的?以客戶端往服務端發送數據爲例:java

  • 客戶端首次鏈接,服務端會將密鑰發送一份到客戶端(密鑰的發送沒有加密),因此客戶端在首次發送數據前,其密鑰同服務端一致,即客戶端加密密鑰(cli_encrypt_key) == 客戶端解密密鑰(cli_decrypt_key) == 服務端加密密鑰(srv_encrypt_key) == 服務端解密密鑰(srv_decrypt_key)函數

  • 客戶端再接收到來此服務端的密鑰後開始向服務端發送加密數據(例子中的raw_data),流程以下:加密

    • 數據變化: encrypt(cli_encrypt_key(old), raw_data) => encrypt_data
    • 密鑰變化: raw_data[:4] & cli_encrypt_key(old) => cli_encrypt_key(new)
  • 服務端接收到客戶端發送的加密數據encrypt_data,解密流程以下:spa

    • 數據變化: decrypt(srv_decrypt_key(old), encrypt_data) => plain_data
    • 密鑰變化: plain_data[:4] & srv_decrypt_key(old) => srv_decrypt_key(new)

由於srv_decrypt_key(old) == cli_encrypt_key(old),且encrypt和decrypt互爲逆操做,因此plain_data == raw_data,因此cli_encrypt_key(new) == srv_decrypt_key(new),所以下次客戶端再次發送來消息,服務端依然可以正常解析(密鑰相同),利用發送消息的前四個字節來生成下次所需的密鑰能夠保證即便一樣的數據每次加密後的密文也不一樣,不易被破解。可是這種方式也有弊端,若是服務端和客戶端中間存在丟包的問題,那麼就必須重連,不然後續就沒法正常解析,同理能夠知道服務端往客戶端發送數據流程了3d

相關文章
相關標籤/搜索