From 8c294cb9eb907c72282c03b79630cea8c9995fcf Mon Sep 17 00:00:00 2001 From: Him188moe Date: Fri, 9 Aug 2019 00:06:41 +0800 Subject: [PATCH 1/2] update --- .../net/mamoe/mirai/network/Protocol.java | 61 +++ .../packet/client/ClientLoginPacket.java | 36 +- .../network/packet/client/ClientPacket.java | 68 +-- .../net/mamoe/mirai/util/TEAEncryption.java | 14 +- .../net/mamoe/mirai/util/_TEAEncryption.java | 456 ++++++++++++++++++ .../net/mamoe/mirai/util/TeaEncryption.java | 9 + 6 files changed, 594 insertions(+), 50 deletions(-) create mode 100644 mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.java create mode 100644 mirai-core/src/main/java/net/mamoe/mirai/util/_TEAEncryption.java create mode 100644 mirai-native/src/main/java/net/mamoe/mirai/util/TeaEncryption.java diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.java b/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.java new file mode 100644 index 000000000..7cf2326f5 --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/Protocol.java @@ -0,0 +1,61 @@ +package net.mamoe.mirai.network; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Him188moe @ Mirai Project + */ +public interface Protocol { + List SERVER_IP = new ArrayList<>() {{ + add("183.60.56.29"); + + List.of( + "sz2.tencent.com", + "sz3.tencent.com", + "sz4.tencent.com", + "sz5.tencent.com", + "sz6.tencent.com", + "sz8.tencent.com", + "sz9.tencent.com" + ).forEach(s -> { + try { + SERVER_IP.add(InetAddress.getByName(s).getHostAddress()); + } catch (UnknownHostException ignored) { + } + }); + }}; + + + String head = "02"; + String ver = "37 13 "; + String fixVer = "03 00 00 00 01 2E 01 00 00 68 52 00 00 00 00 "; + String tail = " 03"; + String _fixVer = "02 00 00 00 01 01 01 00 00 68 20 "; + String _0825data0 = "00 18 00 16 00 01 "; + String _0825data2 = "00 00 04 53 00 00 00 01 00 00 15 85 "; + String _0825key = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"; + String redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"; + String publicKey = "02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3"; + String shareKey = "1A E9 7F 7D C9 73 75 98 AC 02 E0 80 5F A9 C6 AF"; + String _0836fix = "06 A9 12 97 B7 F8 76 25 AF AF D3 EA B4 C8 BC E7 "; + + String _00BaKey = "C1 9C B8 C8 7B 8C 81 BA 9E 9E 7A 89 E1 7A EC 94"; + String _00BaFixKey = "69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A"; + + String encryptKey = "“BA 42 FF 01 CF B4 FF D2 12 F0 6E A7 1B 7C B3 08”"; + + + static byte[] hexToBytes(String hex) { + var list = Arrays.stream(hex.split(" ")).map(String::trim).map(s -> Byte.valueOf(s, 16)).collect(Collectors.toList()); + var buff = new byte[list.size()]; + for (int i = 0; i < list.size(); i++) { + buff[i] = list.get(i); + } + return buff; + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java index 5a5a2fd00..dd9b53c46 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java @@ -1,14 +1,44 @@ package net.mamoe.mirai.network.packet.client; +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.mamoe.mirai.network.Protocol; import net.mamoe.mirai.network.packet.PacketId; +import net.mamoe.mirai.util.TEAEncryption; + +import java.io.IOException; /** * @author Him188moe @ Mirai Project */ -@PacketId(0x08_25_31_01)// TODO: 2019/8/8 +@EqualsAndHashCode(callSuper = true) +@Data +@PacketId(0x08_25_31_01) public class ClientLoginPacket extends ClientPacket { - @Override - public void encode() { + public long qq; + @Override + public void encode() throws IOException { + this.writeQQ(qq); + this.writeHex(Protocol.fixVer); + this.writeHex(Protocol._0825key); + + + //TEA 加密 + var data = new ClientPacket() { + @Override + public void encode() throws IOException { + this.writeHex(Protocol._0825data0); + this.writeHex(Protocol._0825data2); + this.writeQQ(qq); + this.writeHex("00 00 00 00 03 09 00 08 00 01"); + this.writeIp(Protocol.SERVER_IP.get(2)); + this.writeHex("00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19"); + this.writeHex(Protocol.publicKey); + } + }; + data.encode(); + + TEAEncryption.encrypt(data.toByteArray(), Protocol.hexToBytes(Protocol._0825key)); } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java index dbe38579c..ac3dc27f7 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java @@ -1,13 +1,13 @@ package net.mamoe.mirai.network.packet.client; import lombok.Getter; +import net.mamoe.mirai.network.Protocol; import net.mamoe.mirai.network.packet.Packet; import net.mamoe.mirai.network.packet.PacketId; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.Arrays; /** * @author Him188moe @ Mirai Project @@ -25,60 +25,32 @@ public abstract class ClientPacket extends DataOutputStream implements Packet { packageId = annotation.value(); try { - writeHead(); - writeVersion(); + this.writeHex(Protocol.head); + this.writeHex(Protocol.ver); writePacketId(); } catch (IOException e) { throw new RuntimeException(e); } } - public static void main(String[] args) throws IOException { - var pk = new ClientPacket() { - @Override - public void encode() throws IOException { - writeHead(); - } - }; - pk.encode(); - System.out.println(Arrays.toString(((ByteArrayOutputStream) pk.out).toByteArray())); - } - - protected void writeHead() throws IOException { - this.writeByte(0x02); - } - - protected void writeVersion() throws IOException { - this.writeByte(0x37_13); + protected void writeIp(String ip) throws IOException { + for (String s : ip.split("\\.")) { + this.writeInt(Integer.parseInt(s)); + } } protected void writePacketId() throws IOException { - this.writeByte(this.packageId); + this.writeInt(this.packageId); } - protected void writeFixVer() throws IOException { - this.writeByte(0x03); - this.writeByte(0x00); - this.writeByte(0x00); - this.writeByte(0x00); - this.writeByte(0x01); - this.writeByte(0x2E); - this.writeByte(0x01); - this.writeByte(0x00); - this.writeByte(0x00); - this.writeByte(0x68); - this.writeByte(0x52); - this.writeByte(0x00); - this.writeByte(0x00); - this.writeByte(0x00); - this.writeByte(0x00); - } - - protected void write0825Key() throws IOException { - this.writeLong(0xA4_F1_91_88); - this.writeLong(0xC9_82_14_99); - this.writeLong(0x0C_9E_56_55); - this.writeLong(0x91_23_C8_3D); + protected void writeHex(String hex) throws IOException { + for (String s : hex.split(" ")) { + s = s.trim(); + if (s.isEmpty()) { + continue; + } + this.writeByte(Byte.valueOf(s, 16)); + } } protected void writeQQ(long qq) throws IOException { @@ -87,7 +59,13 @@ public abstract class ClientPacket extends DataOutputStream implements Packet { /** - * Encode this packet + * Encode this packet. + * + * Before sending the packet, an {@linkplain Protocol#tail tail} will be added. */ public abstract void encode() throws IOException; + + public byte[] toByteArray() { + return ((ByteArrayOutputStream) this.out).toByteArray(); + } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/util/TEAEncryption.java b/mirai-core/src/main/java/net/mamoe/mirai/util/TEAEncryption.java index b89a14575..e1e22a018 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/util/TEAEncryption.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/util/TEAEncryption.java @@ -1,10 +1,20 @@ package net.mamoe.mirai.util; /** - * TEA encryption - * * @author Him188moe @ Mirai Project */ public final class TEAEncryption { + public static byte[] encrypt(byte[] source, byte[] key) { + return new _TEAEncryption().encrypt(source, key); + } + + public byte[] decrypt(byte[] source, byte[] key) { + return new _TEAEncryption().decrypt(source, key); + } + + public byte[] decrypt(byte[] source, int offset, int length, byte[] key) { + return new _TEAEncryption().decrypt(source, offset, length, key); + } } + diff --git a/mirai-core/src/main/java/net/mamoe/mirai/util/_TEAEncryption.java b/mirai-core/src/main/java/net/mamoe/mirai/util/_TEAEncryption.java new file mode 100644 index 000000000..003ff1b7c --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/util/_TEAEncryption.java @@ -0,0 +1,456 @@ +package net.mamoe.mirai.util; + +import java.io.ByteArrayOutputStream; +import java.util.Random; + +/** + * 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节 + * 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块 + * f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ + * prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ + * preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数 + * 填充的字节数与原始明文长度有关,填充的方法是: + * + *
+ * 
+ *
+ *      ------- 消息填充算法 -----------
+ *      a = (明文长度 + 10) mod 8
+ *      if(a 不等于 0) a = 8 - a;
+ *      b = 随机数 & 0xF8 | a;              这个的作用是把a的值保存了下来
+ *      plain[0] = b;                   然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
+ *      plain[1 至 a+2] = 随机数 & 0xFF;    这里用随机数填充明文的第1到第a+2个字节
+ *      plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
+ *      plain[a+3+明文长度, 最后] = 0;       在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
+ *      ------- 消息填充算法 ------------
+ *
+ * 
+ * 
+ * + * @author https://juejin.im/post/5c5bdf8cf265da2dc706d532 + * @author luma + * @author notXX + */ +final class _TEAEncryption { + // 指向当前的明文块 + private byte[] plain; + // 这指向前面一个明文块 + private byte[] prePlain; + // 输出的密文或者明文 + private byte[] out; + // 当前加密的密文位置和上一次加密的密文块位置,他们相差8 + private int crypt, preCrypt; + // 当前处理的加密解密块的位置 + private int pos; + // 填充数 + private int padding; + // 密钥 + private byte[] key; + // 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的 + // 但是最开始的8个字节没有反馈可用,所有需要标明这种情况 + private boolean header = true; + // 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时 + // 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错 + private int contextStart; + // 随机数对象 + private static Random random = new Random(); + // 字节输出流 + private ByteArrayOutputStream baos; + + /** + * 构造函数 + */ + public _TEAEncryption() { + baos = new ByteArrayOutputStream(8); + } + + /** + * 把字节数组从offset开始的len个字节转换成一个unsigned int, 因为java里面没有unsigned,所以unsigned + * int使用long表示的, 如果len大于8,则认为len等于8。如果len小于8,则高位填0
+ * (edited by notxx) 改变了算法, 性能稍微好一点. 在我的机器上测试10000次, 原始算法花费18s, 这个算法花费12s. + * + * @param in 字节数组. + * @param offset 从哪里开始转换. + * @param len 转换长度, 如果len超过8则忽略后面的 + * @return + */ + private static long getUnsignedInt(byte[] in, int offset, int len) { + long ret = 0; + int end = 0; + if (len > 8) + end = offset + 8; + else + end = offset + len; + for (int i = offset; i < end; i++) { + ret <<= 8; + ret |= in[i] & 0xff; + } + return (ret & 0xffffffffL) | (ret >>> 32); + } + + /** + * 解密 + * + * @param in 密文 + * @param offset 密文开始的位置 + * @param len 密文长度 + * @param k 密钥 + * @return 明文 + */ + public byte[] decrypt(byte[] in, int offset, int len, byte[] k) { + // 检查密钥 + if (k == null) + return null; + + crypt = preCrypt = 0; + this.key = k; + int count; + byte[] m = new byte[offset + 8]; + + // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况 + if ((len % 8 != 0) || (len < 16)) return null; + // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与 + prePlain = decipher(in, offset); + pos = prePlain[0] & 0x7; + // 得到真正明文的长度 + count = len - pos - 10; + // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回 + if (count < 0) return null; + + // 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时 + // 第一个8字节块也没有preCrypt,所有这里建一个全0的 + for (int i = offset; i < m.length; i++) + m[i] = 0; + // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区 + out = new byte[count]; + // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用 + preCrypt = 0; + // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了 + crypt = 8; + // 自然这个也是8 + contextStart = 8; + // 加1,和加密算法是对应的 + pos++; + + // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块 + // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了 + // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来 + // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充 + padding = 1; + while (padding <= 2) { + if (pos < 8) { + pos++; + padding++; + } + if (pos == 8) { + m = in; + if (!decrypt8Bytes(in, offset, len)) return null; + } + } + + // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密 + // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了 + int i = 0; + while (count != 0) { + if (pos < 8) { + out[i] = (byte) (m[offset + preCrypt + pos] ^ prePlain[pos]); + i++; + count--; + pos++; + } + if (pos == 8) { + m = in; + preCrypt = crypt - 8; + if (!decrypt8Bytes(in, offset, len)) + return null; + } + } + + // 最后的解密部分,上面一个while已经把明文都解出来了,就剩下尾部的填充了,应该全是0 + // 所以这里有检查是否解密了之后是不是0,如果不是的话那肯定出错了,返回null + for (padding = 1; padding < 8; padding++) { + if (pos < 8) { + if ((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0) + return null; + pos++; + } + if (pos == 8) { + m = in; + preCrypt = crypt; + if (!decrypt8Bytes(in, offset, len)) + return null; + } + } + return out; + } + + /** + * @param in 需要被解密的密文 + * @param k 密钥 + * @return Message 已解密的消息 + * @paraminLen 密文长度 + */ + public byte[] decrypt(byte[] in, byte[] k) { + return decrypt(in, 0, in.length, k); + } + + /** + * 加密 + * + * @param in 明文字节数组 + * @param offset 开始加密的偏移 + * @param len 加密长度 + * @param k 密钥 + * @return 密文字节数组 + */ + public byte[] encrypt(byte[] in, int offset, int len, byte[] k) { + // 检查密钥 + if (k == null) + return in; + + plain = new byte[8]; + prePlain = new byte[8]; + pos = 1; + padding = 0; + crypt = preCrypt = 0; + this.key = k; + header = true; + + // 计算头部填充字节数 + pos = (len + 0x0A) % 8; + if (pos != 0) + pos = 8 - pos; + // 计算输出的密文长度 + out = new byte[len + pos + 10]; + // 这里的操作把pos存到了plain的第一个字节里面 + // 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置 + plain[0] = (byte) ((rand() & 0xF8) | pos); + + // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容 + for (int i = 1; i <= pos; i++) + plain[i] = (byte) (rand() & 0xFF); + pos++; + // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块 + for (int i = 0; i < 8; i++) + prePlain[i] = 0x0; + + // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之 + padding = 1; + while (padding <= 2) { + if (pos < 8) { + plain[pos++] = (byte) (rand() & 0xFF); + padding++; + } + if (pos == 8) + encrypt8Bytes(); + } + + // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完 + int i = offset; + while (len > 0) { + if (pos < 8) { + plain[pos++] = in[i++]; + len--; + } + if (pos == 8) + encrypt8Bytes(); + } + + // 最后填上0,以保证是8字节的倍数 + padding = 1; + while (padding <= 7) { + if (pos < 8) { + plain[pos++] = 0x0; + padding++; + } + if (pos == 8) + encrypt8Bytes(); + } + + return out; + } + + /** + * @param in 需要加密的明文 + * @param k 密钥 + * @return Message 密文 + * @paraminLen 明文长度 + */ + public byte[] encrypt(byte[] in, byte[] k) { + return encrypt(in, 0, in.length, k); + } + + /** + * 加密一个8字节块 + * + * @param in 明文字节数组 + * @return 密文字节数组 + */ + private byte[] encipher(byte[] in) { + // 迭代次数,16次 + int loop = 0x10; + // 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数 + // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的 + // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与 + long y = getUnsignedInt(in, 0, 4); + long z = getUnsignedInt(in, 4, 4); + long a = getUnsignedInt(key, 0, 4); + long b = getUnsignedInt(key, 4, 4); + long c = getUnsignedInt(key, 8, 4); + long d = getUnsignedInt(key, 12, 4); + // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢? + // 这个数是TEA算法的delta,实际是就是(sqr(5) - 1) * 2^31 (根号5,减1,再乘2的31次方) + long sum = 0; + long delta = 0x9E3779B9; + delta &= 0xFFFFFFFFL; + + // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去 + while (loop-- > 0) { + sum += delta; + sum &= 0xFFFFFFFFL; + y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b); + y &= 0xFFFFFFFFL; + z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d); + z &= 0xFFFFFFFFL; + } + + // 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int + baos.reset(); + writeInt((int) y); + writeInt((int) z); + return baos.toByteArray(); + } + + /** + * 解密从offset开始的8字节密文 + * + * @param in 密文字节数组 + * @param offset 密文开始位置 + * @return 明文 + */ + private byte[] decipher(byte[] in, int offset) { + // 迭代次数,16次 + int loop = 0x10; + // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数 + // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的 + // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与 + long y = getUnsignedInt(in, offset, 4); + long z = getUnsignedInt(in, offset + 4, 4); + long a = getUnsignedInt(key, 0, 4); + long b = getUnsignedInt(key, 4, 4); + long c = getUnsignedInt(key, 8, 4); + long d = getUnsignedInt(key, 12, 4); + // 算法的一些控制变量,sum在这里也有数了,这个sum和迭代次数有关系 + // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后 + // 得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样才能解密呀~~ + long sum = 0xE3779B90; + sum &= 0xFFFFFFFFL; + long delta = 0x9E3779B9; + delta &= 0xFFFFFFFFL; + + // 迭代开始了, @_@ + while (loop-- > 0) { + z -= ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d); + z &= 0xFFFFFFFFL; + y -= ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b); + y &= 0xFFFFFFFFL; + sum -= delta; + sum &= 0xFFFFFFFFL; + } + + baos.reset(); + writeInt((int) y); + writeInt((int) z); + return baos.toByteArray(); + } + + /** + * 写入一个整型到输出流,高字节优先 + * + * @param t + */ + private void writeInt(int t) { + baos.write(t >>> 24); + baos.write(t >>> 16); + baos.write(t >>> 8); + baos.write(t); + } + + /** + * 解密 + * + * @param in 密文 + * @return 明文 + */ + private byte[] decipher(byte[] in) { + return decipher(in, 0); + } + + /** + * 加密8字节 + */ + private void encrypt8Bytes() { + // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用 + for (pos = 0; pos < 8; pos++) { + if (header) + plain[pos] ^= prePlain[pos]; + else + plain[pos] ^= out[preCrypt + pos]; + } + // 这个完成我上面说的 f(plain ^ preCrypt) + byte[] crypted = encipher(plain); + // 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了 + System.arraycopy(crypted, 0, out, crypt, 8); + + // 这个完成了 f(plain ^ preCrypt) ^ prePlain,ok,下面拷贝一下就行了 + for (pos = 0; pos < 8; pos++) + out[crypt + pos] ^= prePlain[pos]; + System.arraycopy(plain, 0, prePlain, 0, 8); + + // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了 + preCrypt = crypt; + crypt += 8; + pos = 0; + header = false; + } + + /** + * 解密8个字节 + * + * @param in 密文字节数组 + * @param offset 从何处开始解密 + * @param len 密文的长度 + * @return true表示解密成功 + */ + private boolean decrypt8Bytes(byte[] in, int offset, int len) { + // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain + for (pos = 0; pos < 8; pos++) { + if (contextStart + pos >= len) + return true; + prePlain[pos] ^= in[offset + crypt + pos]; + } + + // 好,这里执行到了 d(crypt ^ prePlain) + prePlain = decipher(prePlain); + if (prePlain == null) + return false; + + // 解密完成,最后一步好像没做? + // 这里最后一步放到decrypt里面去做了,因为解密的步骤有点不太一样 + // 调整这些变量的值先 + contextStart += 8; + crypt += 8; + pos = 0; + return true; + } + + /** + * 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值 + * 随机因子可以使相同的明文每次加密出来的密文都不一样 + * + * @return 随机因子 + */ + private int rand() { + return random.nextInt(); + } +} diff --git a/mirai-native/src/main/java/net/mamoe/mirai/util/TeaEncryption.java b/mirai-native/src/main/java/net/mamoe/mirai/util/TeaEncryption.java new file mode 100644 index 000000000..f14480377 --- /dev/null +++ b/mirai-native/src/main/java/net/mamoe/mirai/util/TeaEncryption.java @@ -0,0 +1,9 @@ +package net.mamoe.mirai.util; + +/** + * @author Him188moe @ Mirai Project + */ +public final class TeaEncryption { + + public static native int Decrypt(); +} From 43595db045f5aab17d6f58f21a29b883854a5903 Mon Sep 17 00:00:00 2001 From: Him188moe Date: Fri, 9 Aug 2019 00:19:12 +0800 Subject: [PATCH 2/2] update --- .../packet/client/ClientHeartbeatPacket.java | 16 ++++++++++++++-- .../network/packet/client/ClientLoginPacket.java | 2 +- .../network/packet/client/ClientPacket.java | 10 ++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientHeartbeatPacket.java b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientHeartbeatPacket.java index 6575a22d4..790175142 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientHeartbeatPacket.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientHeartbeatPacket.java @@ -1,16 +1,28 @@ package net.mamoe.mirai.network.packet.client; +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.mamoe.mirai.network.Protocol; import net.mamoe.mirai.network.packet.PacketId; +import net.mamoe.mirai.util.TEAEncryption; import java.io.IOException; /** * @author Him188moe @ Mirai Project */ -@PacketId(0x0058) +@EqualsAndHashCode(callSuper = true) +@Data +@PacketId(0x00_58) public class ClientHeartbeatPacket extends ClientPacket { + public long qq; + public byte[] sessionKey;//登录后获得 + @Override public void encode() throws IOException { - + this.writeRandom(2); + this.writeQQ(qq); + this.writeHex(Protocol.fixVer); + this.write(TEAEncryption.encrypt(new byte[]{0x00, 0x01, 0x00, 0x01}, sessionKey)); } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java index dd9b53c46..54c82f055 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientLoginPacket.java @@ -39,6 +39,6 @@ public class ClientLoginPacket extends ClientPacket { }; data.encode(); - TEAEncryption.encrypt(data.toByteArray(), Protocol.hexToBytes(Protocol._0825key)); + this.write(TEAEncryption.encrypt(data.toByteArray(), Protocol.hexToBytes(Protocol._0825key))); } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java index ac3dc27f7..09e493e72 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/client/ClientPacket.java @@ -53,6 +53,12 @@ public abstract class ClientPacket extends DataOutputStream implements Packet { } } + protected void writeRandom(int length) throws IOException { + for (int i = 0; i < length; i++) { + this.writeByte((byte) (int) (Math.random() * 255)); + } + } + protected void writeQQ(long qq) throws IOException { this.writeLong(qq); } @@ -60,9 +66,9 @@ public abstract class ClientPacket extends DataOutputStream implements Packet { /** * Encode this packet. - * + *

* Before sending the packet, an {@linkplain Protocol#tail tail} will be added. - */ + */// TODO: 2019/8/9 添加 tail public abstract void encode() throws IOException; public byte[] toByteArray() {