天堂通信加密解析

天堂加解密代码(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函数互为逆操作,说明只要加密和解密的密钥相同,那么就能够还原数据,那么问题就变成了,客户端和服务端是如何保证密钥相同的?以客户端往服务端发送数据为例:

  • 客户端首次连接,服务端会将密钥发送一份到客户端(密钥的发送没有加密),所以客户端在首次发送数据前,其密钥同服务端一致,即客户端加密密钥(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,解密流程如下:

    • 数据变化: 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),因此下次客户端再次发送来消息,服务端依然能够正常解析(密钥相同),利用发送消息的前四个字节来生成下次所需的密钥可以保证即使同样的数据每次加密后的密文也不同,不易被破解。但是这种方式也有弊端,如果服务端和客户端中间存在丢包的问题,那么就必须重连,否则后续就无法正常解析,同理可以知道服务端往客户端发送数据流程了

posted @ 2018-06-14 12:07  银魔术师  阅读(689)  评论(0编辑  收藏  举报