mirror of
https://github.com/fumiama/simple-dict-android.git
synced 2026-06-05 00:30:24 +08:00
v4.0
1. 使用新式封包 2. 使用tea加密算法
This commit is contained in:
@@ -12,8 +12,8 @@ android {
|
||||
applicationId "top.fumiama.simpledict"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 31
|
||||
versionCode 18
|
||||
versionName '3.1.1'
|
||||
versionCode 19
|
||||
versionName '4.0'
|
||||
resConfigs "zh", "zh-rCN"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -2,6 +2,7 @@ package top.fumiama.simpledict
|
||||
//Fumiama 20210601
|
||||
//Client.kt
|
||||
import android.util.Log
|
||||
import top.fumiama.simpledict.Utils.toHexStr
|
||||
import java.io.*
|
||||
import java.lang.Thread.sleep
|
||||
import java.net.Socket
|
||||
@@ -52,7 +53,7 @@ class Client(private val ip: String, private val port: Int) {
|
||||
if (message != null) { //判断输出流或者消息是否为空,为空的话会产生null pointer错误
|
||||
dout?.write(message)
|
||||
dout?.flush()
|
||||
Log.d("MyC", "Send msg: ${message.decodeToString()}")
|
||||
Log.d("MyC", "Send msg: ${toHexStr(message)}")
|
||||
return true
|
||||
} else Log.d("MyC", "The message to be sent is empty")
|
||||
Log.d("MyC", "send message succeed")
|
||||
@@ -92,7 +93,7 @@ class Client(private val ip: String, private val port: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
fun receiveMessage(totalSize: Int) = receiveRawMessage(totalSize).decodeToString()
|
||||
//fun receiveMessage(totalSize: Int) = receiveRawMessage(totalSize).decodeToString()
|
||||
|
||||
/**
|
||||
* 关闭连接
|
||||
|
||||
56
app/src/main/java/top/fumiama/simpledict/CmdPacket.java
Normal file
56
app/src/main/java/top/fumiama/simpledict/CmdPacket.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package top.fumiama.simpledict;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CmdPacket {
|
||||
private final byte cmd;
|
||||
private final byte[] data;
|
||||
private final byte[] md5;
|
||||
private final Tea t;
|
||||
|
||||
public CmdPacket(byte cmd, @NonNull byte[] data, @NonNull Tea t) throws NoSuchAlgorithmException {
|
||||
this.cmd = cmd;
|
||||
this.data = data;
|
||||
this.t = t;
|
||||
md5 = MessageDigest.getInstance("MD5").digest(data);
|
||||
Log.d("MyCP", "md5: "+Utils.INSTANCE.toHexStr(md5));
|
||||
}
|
||||
|
||||
public CmdPacket(@NonNull byte[] raw, @NonNull Tea t) {
|
||||
this.cmd = raw[0];
|
||||
this.t = t;
|
||||
md5 = new byte[16];
|
||||
Log.d("MyCP", "build from raw packet: "+Utils.INSTANCE.toHexStr(raw));
|
||||
System.arraycopy(raw, 2, md5, 0, 16);
|
||||
Log.d("MyCP", "md5: "+Utils.INSTANCE.toHexStr(md5));
|
||||
data = new byte[raw.length-1-1-16];
|
||||
System.arraycopy(raw, 1+1+16, data, 0, data.length);
|
||||
Log.d("MyCP", "data length: "+data.length);
|
||||
}
|
||||
|
||||
public @NonNull byte[] encrypt(byte seq) {
|
||||
byte[] dat = t.encryptLittleEndian(data, seq);
|
||||
byte[] d = new byte[1+1+16+dat.length];
|
||||
d[0] = cmd;
|
||||
d[1] = (byte) dat.length;
|
||||
System.arraycopy(md5, 0, d, 2, 16);
|
||||
System.arraycopy(dat, 0, d, 1+1+16, dat.length);
|
||||
return d;
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte seq) throws NoSuchAlgorithmException {
|
||||
byte[] dat = t.decryptLittleEndian(data, seq);
|
||||
if (dat != null && Arrays.equals(MessageDigest.getInstance("MD5").digest(dat), md5)) {
|
||||
return dat;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final static byte CMDGET = 0, CMDCAT = 1, CMDMD5 = 2, CMDACK = 3, CMDEND = 4, CMDSET = 5, CMDDEL = 6, CMDDAT = 7;
|
||||
}
|
||||
@@ -5,13 +5,17 @@ import java.io.File
|
||||
import java.lang.Thread.sleep
|
||||
import java.security.MessageDigest
|
||||
|
||||
class SimpleDict(private val client: Client, private val pwd: String, private val externalCacheDir: File?, private val spwd: String?) { //must run in thread
|
||||
class SimpleDict(private val client: Client, pwd: String, private val externalCacheDir: File?, spwd: String?) { //must run in thread
|
||||
private var dict = HashMap<String, String?>()
|
||||
val size get() = dict.size
|
||||
val keys get() = dict.keys
|
||||
var latestKeys = arrayOf<String>()
|
||||
private var seq: Byte = 0
|
||||
private val ptea = Tea(pwd.toByteArray())
|
||||
private val stea = spwd?.let { Tea(it.toByteArray()) }
|
||||
private val md5File = File(externalCacheDir, "md5")
|
||||
private val dspFile = File(externalCacheDir, "dsp")
|
||||
private val filler = "fill".toByteArray()
|
||||
private val raw: ByteArray?
|
||||
get() {
|
||||
var times = 3
|
||||
@@ -19,7 +23,7 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
var exit = false
|
||||
while(times-- > 0 && !exit) {
|
||||
if(initDict()) {
|
||||
client.sendMessage("cat")
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDCAT, filler, ptea).encrypt(seq))
|
||||
try {
|
||||
var length = ""
|
||||
var c = client.read()
|
||||
@@ -28,7 +32,8 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
c = client.read()
|
||||
}
|
||||
Log.d("MySD", "length: $length")
|
||||
re = client.receiveRawMessage(length.toInt())
|
||||
re = ptea.decryptLittleEndian(client.receiveRawMessage(length.toInt()), (seq+1).toByte())
|
||||
if(re != null) seq = (seq + 2).toByte()
|
||||
exit = true
|
||||
} catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
@@ -38,18 +43,21 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
}
|
||||
return re
|
||||
}
|
||||
|
||||
private fun initDict(): Boolean {
|
||||
if(client.initConnect()){
|
||||
if(client.sendMessage(pwd)) {
|
||||
return client.receiveRawMessage(31).size == 31
|
||||
}
|
||||
private val ack: ByteArray?
|
||||
get() {
|
||||
var re = client.receiveRawMessage(1+1+16)
|
||||
re += client.receiveRawMessage(re[1].toInt())
|
||||
val r = CmdPacket(re, ptea).decrypt(seq)
|
||||
if (r != null) seq++
|
||||
Log.d("MySD", "ack: ${r?.decodeToString()}")
|
||||
return r
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun initDict() = client.initConnect()
|
||||
|
||||
private fun closeDict(): Boolean {
|
||||
client.sendMessage("quit")
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDEND, filler, ptea).encrypt(seq))
|
||||
seq = 0
|
||||
return client.closeConnect()
|
||||
}
|
||||
|
||||
@@ -63,12 +71,11 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
|
||||
private fun hasNewItem(md5: ByteArray): Boolean =
|
||||
if(initDict()) {
|
||||
client.sendMessage("md5".toByteArray() + md5)
|
||||
client.receiveRawMessage(3) //md5
|
||||
val re = client.receiveMessage(4)
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDMD5, md5, ptea).encrypt(seq++))
|
||||
val cp = ack
|
||||
Log.d("MySD", "Check md5: ${cp?.decodeToString()}")
|
||||
closeDict()
|
||||
Log.d("MySD", "Check md5: $re")
|
||||
re == "nequ"
|
||||
cp?.decodeToString() == "nequ"
|
||||
} else false
|
||||
|
||||
private fun analyzeDict(datas: ByteArray, saveDict: Boolean) {
|
||||
@@ -109,13 +116,10 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
}
|
||||
|
||||
fun del(key: String): Boolean {
|
||||
if(spwd == null) return false
|
||||
if(stea == null) return false
|
||||
else if(initDict()) {
|
||||
val delPass = "del$spwd"
|
||||
client.sendMessage(delPass)
|
||||
client.receiveRawMessage(delPass.length)
|
||||
client.sendMessage(key)
|
||||
if(client.receiveMessage(4) == "succ") {
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDDEL, key.toByteArray(), stea).encrypt(seq++))
|
||||
if(ack?.decodeToString() == "succ") {
|
||||
if(closeDict()) {
|
||||
dict.remove(key)
|
||||
val end = latestKeys.size-1
|
||||
@@ -134,13 +138,10 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
}
|
||||
|
||||
private fun sendel(key: ByteArray): Boolean {
|
||||
if(spwd == null) return false
|
||||
if(stea == null) return false
|
||||
else if(initDict()) {
|
||||
val delPass = "del$spwd"
|
||||
client.sendMessage(delPass)
|
||||
client.receiveRawMessage(delPass.length)
|
||||
client.sendMessage(key)
|
||||
if(client.receiveMessage(4) == "succ") {
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDDEL, key, stea).encrypt(seq++))
|
||||
if(ack?.decodeToString() == "succ") {
|
||||
return closeDict()
|
||||
} else closeDict()
|
||||
}
|
||||
@@ -151,18 +152,16 @@ class SimpleDict(private val client: Client, private val pwd: String, private va
|
||||
|
||||
fun set(key: String, value: String): Boolean {
|
||||
//if(spwd == null) return false
|
||||
if(stea == null) return false
|
||||
val contain = dict.containsKey(key)
|
||||
if((contain && sendel(key.toByteArray())) || !contain) {
|
||||
if(initDict()) {
|
||||
val setPass = "set$spwd"
|
||||
client.sendMessage(setPass)
|
||||
client.receiveRawMessage(setPass.length)
|
||||
client.sendMessage(key)
|
||||
if(client.receiveMessage(4) == "data") {
|
||||
client.sendMessage(value)
|
||||
client.receiveMessage(4)
|
||||
if(closeDict()) dict[key] = value
|
||||
return true
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDSET, key.toByteArray(), stea).encrypt(seq++))
|
||||
if(ack?.decodeToString() == "data") {
|
||||
client.sendMessage(CmdPacket(CmdPacket.CMDDAT, value.toByteArray(), stea).encrypt(seq++))
|
||||
val s = ack?.decodeToString() == "succ"
|
||||
if(s) dict[key] = value
|
||||
return closeDict() && s
|
||||
} else closeDict()
|
||||
}
|
||||
return false
|
||||
|
||||
126
app/src/main/java/top/fumiama/simpledict/Tea.java
Normal file
126
app/src/main/java/top/fumiama/simpledict/Tea.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package top.fumiama.simpledict;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Random;
|
||||
|
||||
public class Tea {
|
||||
private final int[] t = new int[4];
|
||||
private final Random r;
|
||||
public Tea(@NonNull byte[] tea) {
|
||||
byte[] tea16 = new byte[16];
|
||||
System.arraycopy(tea, 0, tea16, 0, Math.min(tea.length, 15));
|
||||
tea16[15] = 0;
|
||||
ByteBuffer bf = ByteBuffer.wrap(tea16).order(ByteOrder.LITTLE_ENDIAN);
|
||||
t[0] = bf.getInt(0);
|
||||
t[1] = bf.getInt(4);
|
||||
t[2] = bf.getInt(8);
|
||||
t[3] = bf.getInt(12) & 0x00ffffff;
|
||||
r = new Random();
|
||||
//Log.d("MyTEA", "t: "+ Arrays.toString(t));
|
||||
}
|
||||
|
||||
public @NonNull byte[] encryptLittleEndian(@NonNull byte[] src, byte seq) {
|
||||
int lens = src.length;
|
||||
int fill = 10 - (lens+1)%8;
|
||||
int dstlen = fill+lens+7;
|
||||
byte[] dst = new byte[dstlen];
|
||||
byte[] randfill = new byte[fill-1];
|
||||
t[3] = ((int)seq)<<24 | (t[3]&0x00ffffff);
|
||||
Log.d("MyTEA", "encrypt seq: "+ seq);
|
||||
r.nextBytes(randfill);
|
||||
//Log.d("MyTEA", "rand fill: "+ Utils.INSTANCE.toHexStr(randfill));
|
||||
System.arraycopy(randfill, 0, dst, 1, fill-1);
|
||||
dst[0] = (byte)((fill-3)|0xF8); // 存储pad长度
|
||||
System.arraycopy(src, 0, dst, fill, lens);
|
||||
//Log.d("MyTEA", "dst before enc: "+Utils.INSTANCE.toHexStr(dst));
|
||||
|
||||
long iv1 = 0, iv2 = 0, holder;
|
||||
ByteBuffer bf = ByteBuffer.wrap(dst).order(ByteOrder.LITTLE_ENDIAN);
|
||||
for(int i = 0; i < dstlen; i += 8) {
|
||||
long block = bf.getLong(i);
|
||||
holder = block ^ iv1;
|
||||
|
||||
int v0 = (int)(holder>>32);
|
||||
int v1 = (int)holder;
|
||||
for (int j = 0; j < 0x10; j++) {
|
||||
v0 += (v1 + sumtable[j]) ^ ((int)(((long) v1 << 4)&0x00000000fffffff0L) + t[0]) ^ ((int)((v1 >> 5)&0x07ffffff) + t[1]);
|
||||
v1 += (v0 + sumtable[j]) ^ ((int)(((long) v0 << 4)&0x00000000fffffff0L) + t[2]) ^ ((int)((v0 >> 5)&0x07ffffff) + t[3]);
|
||||
}
|
||||
//Log.d("MyTEA", "v0: "+Integer.toHexString(v0)+", v1: "+Integer.toHexString(v1));
|
||||
iv1 = (((long)v0)<<32) | (((long)v1)&0x00000000ffffffffL);
|
||||
//Log.d("MyTEA", "iv1: "+Long.toHexString(iv1));
|
||||
|
||||
iv1 = iv1 ^ iv2;
|
||||
iv2 = holder;
|
||||
//Log.d("MyTEA", "put: "+Long.toHexString(iv1));
|
||||
bf.putLong(i, iv1);
|
||||
}
|
||||
|
||||
//Log.d("MyTEA", "dst after enc: "+Utils.INSTANCE.toHexStr(dst));
|
||||
return dst;
|
||||
}
|
||||
|
||||
public byte[] decryptLittleEndian(@NonNull byte[] src, byte seq) {
|
||||
if (src.length < 16 || (src.length)%8 != 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] dst = new byte[src.length];
|
||||
|
||||
long iv1, iv2 = 0, holder = 0;
|
||||
t[3] = ((int)seq)<<24 | (t[3]&0x00ffffff);
|
||||
Log.d("MyTEA", "decrypt seq: "+ seq);
|
||||
ByteBuffer sbf = ByteBuffer.wrap(src).order(ByteOrder.LITTLE_ENDIAN);
|
||||
ByteBuffer dbf = ByteBuffer.wrap(dst).order(ByteOrder.LITTLE_ENDIAN);
|
||||
for(int i = 0; i < src.length; i += 8) {
|
||||
iv1 = sbf.getLong(i);
|
||||
|
||||
iv2 ^= iv1;
|
||||
|
||||
int v0 = (int)(iv2>>32);
|
||||
int v1 = (int)iv2;
|
||||
for (int j = 0x0f; j >= 0; j--) {
|
||||
v1 -= (v0 + sumtable[j]) ^ ((int)(((long) v0 << 4)&0x00000000fffffff0L) + t[2]) ^ ((int)((v0 >> 5)&0x07ffffff) + t[3]);
|
||||
v0 -= (v1 + sumtable[j]) ^ ((int)(((long) v1 << 4)&0x00000000fffffff0L) + t[0]) ^ ((int)((v1 >> 5)&0x07ffffff) + t[1]);
|
||||
}
|
||||
iv2 = (((long)v0)<<32) | (((long)v1)&0x00000000ffffffffL);
|
||||
|
||||
dbf.putLong(i, iv2^holder);
|
||||
|
||||
holder = iv1;
|
||||
}
|
||||
|
||||
int start = (dst[0]&7)+3;
|
||||
Log.d("MyTEA", "decrypt start: "+ start);
|
||||
int datlen = src.length-7-start;
|
||||
if(datlen <= 0) return null;
|
||||
byte[] dat = new byte[datlen];
|
||||
Log.d("MyTEA", "decrypt data length: "+datlen);
|
||||
System.arraycopy(dst, start, dat, 0, datlen);
|
||||
return dat;
|
||||
}
|
||||
|
||||
// TEA encoding sumtable
|
||||
private static final int[] sumtable = {
|
||||
0x9e3579b9,
|
||||
0x3c6ef172,
|
||||
0xd2a66d2b,
|
||||
0x78dd36e4,
|
||||
0x17e5609d,
|
||||
0xb54fda56,
|
||||
0x5384560f,
|
||||
0xf1bb77c8,
|
||||
0x8ff24781,
|
||||
0x2e4ac13a,
|
||||
0xcc653af3,
|
||||
0x6a9964ac,
|
||||
0x08d12965,
|
||||
0xa708081e,
|
||||
0x451221d7,
|
||||
0xe37793d0,
|
||||
};
|
||||
}
|
||||
14
app/src/main/java/top/fumiama/simpledict/Utils.kt
Normal file
14
app/src/main/java/top/fumiama/simpledict/Utils.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package top.fumiama.simpledict
|
||||
|
||||
object Utils {
|
||||
fun toHexStr(byteArray: ByteArray) =
|
||||
with(StringBuilder()) {
|
||||
byteArray.forEach {
|
||||
val hex = it.toInt() and (0xFF)
|
||||
val hexStr = Integer.toHexString(hex)
|
||||
if (hexStr.length == 1) append("0").append(hexStr)
|
||||
else append(hexStr)
|
||||
}
|
||||
toString()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user