mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-19 12:39:11 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
e85014e7a6
@ -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<String> 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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
this.write(TEAEncryption.encrypt(data.toByteArray(), Protocol.hexToBytes(Protocol._0825key)));
|
||||
}
|
||||
}
|
||||
|
@ -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,38 @@ 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();
|
||||
protected void writeIp(String ip) throws IOException {
|
||||
for (String s : ip.split("\\.")) {
|
||||
this.writeInt(Integer.parseInt(s));
|
||||
}
|
||||
};
|
||||
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 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 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 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 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 {
|
||||
@ -87,7 +65,13 @@ public abstract class ClientPacket extends DataOutputStream implements Packet {
|
||||
|
||||
|
||||
/**
|
||||
* Encode this packet
|
||||
*/
|
||||
* Encode this packet.
|
||||
* <p>
|
||||
* 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() {
|
||||
return ((ByteArrayOutputStream) this.out).toByteArray();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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字节的倍数
|
||||
* 填充的字节数与原始明文长度有关,填充的方法是:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* ------- 消息填充算法 -----------
|
||||
* 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的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
|
||||
* ------- 消息填充算法 ------------
|
||||
*
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @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 <br>
|
||||
* (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();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package net.mamoe.mirai.util;
|
||||
|
||||
/**
|
||||
* @author Him188moe @ Mirai Project
|
||||
*/
|
||||
public final class TeaEncryption {
|
||||
|
||||
public static native int Decrypt();
|
||||
}
|
Loading…
Reference in New Issue
Block a user