# -*- 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)
python
由于srv_decrypt_key(old) == cli_encrypt_key(old)
,且encrypt和decrypt互为逆操做,因此plain_data == raw_data
,因此cli_encrypt_key(new) == srv_decrypt_key(new)
,所以下次客户端再次发送来消息,服务端依然可以正常解析(密钥相同),利用发送消息的前四个字节来生成下次所需的密钥能够保证即便一样的数据每次加密后的密文也不一样,不易被破解。可是这种方式也有弊端,若是服务端和客户端中间存在丢包的问题,那么就必须重连,不然后续就没法正常解析,同理能够知道服务端往客户端发送数据流程了函数