1
0
mirror of https://github.com/fumiama/simple-dict-android.git synced 2026-06-05 00:30:24 +08:00
1. 使用新式封包
2. 使用tea加密算法
This commit is contained in:
fumiama
2021-12-12 20:54:57 +08:00
parent e9176abfde
commit 804a940dd6
7 changed files with 240 additions and 42 deletions

View File

@@ -2,10 +2,12 @@
<dictionary name="rumia">
<words>
<w>datas</w>
<w>dstlen</w>
<w>fumiama</w>
<w>nequ</w>
<w>slle</w>
<w>spwd</w>
<w>sumtable</w>
</words>
</dictionary>
</component>

View File

@@ -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"

View File

@@ -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()
/**
* 关闭连接

View 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;
}

View File

@@ -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

View 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,
};
}

View 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()
}
}