安卓解密的具体方法我就不再赘述了,大体方法都是正确的,这里只讲我遇到的问题。

其实这个问题应该是在 Java 外的语言中才会出现,安卓 QQ 的加密方法是写在 so 层的,传入的是 Java 层的 String.toCharArray(),然后将每个 char 异或一次。这里就会涉及到编码的问题。

众所周知 UTF-8 是变长编码的,可能的字节数为 1-4。但是 Java 中toCharArray使用的编码并不是 UTF-8,而是 UTF-16BE。在 UTF-8 长度为四个字节的情况下(例如某些 emoji),UTF-16BE 会将其转换为两个 char,如果我们再按照一个 char 去解密,就会出现乱码。

此处给出正确的解密 python 代码:

import struct

def utf8_to_unicode_arr(utf8_bytes):
    string = utf8_bytes.decode('utf-8').encode('utf-16be')
    string = struct.unpack(f'>{len(string)//2}H', string)
    return list(string)


def convert_to_utf8(char_array):
    binary_data = struct.pack(
        f'>{len(char_array)}H', *char_array)
    utf8_bytes = binary_data.decode('utf-16be').encode('utf-8')
    return utf8_bytes.decode('utf-8')


def decrypt(data, key):
    if not data:
        return data
    msg = b''
    if type(data) == bytes:
        msg = b''
        for i in range(0, len(data)):
            msg += bytes([data[i] ^ ord(key[i % len(key)])])
        return msg
    elif type(data) == str:
        code_points = utf8_to_unicode_arr(data.encode("utf-8"))
        for i in range(0, len(code_points)):
            code_points[i] ^= ord(key[i % len(key)])
        return convert_to_utf8(code_points)
    else:
        return data

print(decrypt(b'xWVYQTDXOC\x10[^^OEC\xc2\x90~\x14\xf0\x93\x8c\x8b'.decode(), "02:00:00:00:00:00"))
# 输出:Helianthus annuus L.🌻

当然,在字节长度 1-3 的情况下,直接按照 UTF-8 解密也是可以的,但是在字节长度为 4 的情况下,就会出现乱码,需要特别注意。