From 804a940dd61e6c3b49fd449dd5f57376d1827888 Mon Sep 17 00:00:00 2001 From: fumiama Date: Sun, 12 Dec 2021 20:54:57 +0800 Subject: [PATCH] =?UTF-8?q?v4.0=201.=20=E4=BD=BF=E7=94=A8=E6=96=B0?= =?UTF-8?q?=E5=BC=8F=E5=B0=81=E5=8C=85=202.=20=E4=BD=BF=E7=94=A8tea?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/dictionaries/rumia.xml | 2 + app/build.gradle | 4 +- .../java/top/fumiama/simpledict/Client.kt | 5 +- .../top/fumiama/simpledict/CmdPacket.java | 56 ++++++++ .../java/top/fumiama/simpledict/SimpleDict.kt | 75 +++++------ .../main/java/top/fumiama/simpledict/Tea.java | 126 ++++++++++++++++++ .../main/java/top/fumiama/simpledict/Utils.kt | 14 ++ 7 files changed, 240 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/top/fumiama/simpledict/CmdPacket.java create mode 100644 app/src/main/java/top/fumiama/simpledict/Tea.java create mode 100644 app/src/main/java/top/fumiama/simpledict/Utils.kt diff --git a/.idea/dictionaries/rumia.xml b/.idea/dictionaries/rumia.xml index 6d8dbb9..fe133b9 100644 --- a/.idea/dictionaries/rumia.xml +++ b/.idea/dictionaries/rumia.xml @@ -2,10 +2,12 @@ datas + dstlen fumiama nequ slle spwd + sumtable \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d75b72c..7084b23 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/top/fumiama/simpledict/Client.kt b/app/src/main/java/top/fumiama/simpledict/Client.kt index bc2c0db..30f5157 100644 --- a/app/src/main/java/top/fumiama/simpledict/Client.kt +++ b/app/src/main/java/top/fumiama/simpledict/Client.kt @@ -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() /** * 关闭连接 diff --git a/app/src/main/java/top/fumiama/simpledict/CmdPacket.java b/app/src/main/java/top/fumiama/simpledict/CmdPacket.java new file mode 100644 index 0000000..4ec44b6 --- /dev/null +++ b/app/src/main/java/top/fumiama/simpledict/CmdPacket.java @@ -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; +} diff --git a/app/src/main/java/top/fumiama/simpledict/SimpleDict.kt b/app/src/main/java/top/fumiama/simpledict/SimpleDict.kt index da8e933..e038149 100644 --- a/app/src/main/java/top/fumiama/simpledict/SimpleDict.kt +++ b/app/src/main/java/top/fumiama/simpledict/SimpleDict.kt @@ -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() val size get() = dict.size val keys get() = dict.keys var latestKeys = arrayOf() + 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 diff --git a/app/src/main/java/top/fumiama/simpledict/Tea.java b/app/src/main/java/top/fumiama/simpledict/Tea.java new file mode 100644 index 0000000..7d207f8 --- /dev/null +++ b/app/src/main/java/top/fumiama/simpledict/Tea.java @@ -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, + }; +} diff --git a/app/src/main/java/top/fumiama/simpledict/Utils.kt b/app/src/main/java/top/fumiama/simpledict/Utils.kt new file mode 100644 index 0000000..cddc50d --- /dev/null +++ b/app/src/main/java/top/fumiama/simpledict/Utils.kt @@ -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() + } +} \ No newline at end of file