Preparation
- Phone: Google Nexus 5
Tools:
- APK IDE (APK改之理)
- Package Capture by Grey Shirts
Target app: Neusoft Smart Campus Platform (East China Jiaotong University edition)

Let’s take a quick look at the app.


Log in with the following account:
username:20142110070202
password:011211

Open Package Capture to sniff the traffic.

You can see that during login, the client sends a GET request to the server:
portal.ecjtu.edu.cn/dcp/service?action=0B38EC05B0815AFB59A1B6171DD8378702E08185784ADC47B213462CC176EBC626865CAE40CF998812E25558152958267F28B8BE59532A45283F2A274FA363CB685347955B330164CDEFA8228F011F4BB73C36BFF072DB9C
The server returns a JSON response:
{"message":{"map":{"UNIT_NAME":"软件学院","USER_ID":"20142110070202","UUID":"201412019176","ID_TYPE":"1","UNIT_ID":"1021","USER_PASSWORD":"?h?g?g+t.[=dR);:","USER_ACCOUNT":"20142110070202","USER_SEX":"1","IS_ACTIVE":"1","BEGIN_TIME":"20141219","USER_FIRST_LOGIN":"0","USER_DESCRIPTION":"史泰龙","USER_ACCOUNT_LOCKED":"1","IS_MAIN_IDENTITY":"1","END_TIME":"20600606"}},"success":true}
So the traffic between the mobile client and the server is encrypted.
Open the app in APK IDE.

Smali is hard to read, so click Open Java Source.

Search for the keyword "/dcp/service?action=".
Key code:
public final a a(String paramString1, String paramString2, String paramString3)
{
String str = paramString3;
if (paramString3 == null) {
str = "";
}
paramString1 = "method=checkUserLoginIFS&idNumber=" + paramString1 + "&UserPwd=" + paramString2 + "&logonIP=" + str;
try
{
paramString2 = com.neusoft.edu.v6.ydszxy.hdjiaoda.appcenter.c.b.a.a(paramString1);
paramString1 = paramString2;
}
catch (Exception paramString2)
{
for (;;)
{
paramString2.printStackTrace();
}
}
paramString1 = "/dcp/service?action=" + paramString1;
return (a)a(this.a.a(paramString1), new a());
}
From the source we can see that paramString1 originally looks like:
method=checkUserLoginIFS&idNumber=xxx&UserPwd=xxx&logonIP=xxx
But after being encrypted by:
com.neusoft.edu.v6.ydszxy.hdjiaoda.appcenter.c.b.a.a
it becomes the ciphertext after action= in the GET request. Follow the call to appcenter.c.b.a.a:

Key code:
public static String a(String paramString)
{
Object localObject = new DESedeKeySpec("neusofteducationplatform".getBytes());
localObject = SecretKeyFactory.getInstance("desede").generateSecret((KeySpec)localObject);
Cipher localCipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
localCipher.init(1, (Key)localObject, new IvParameterSpec("01234567".getBytes()));
return a(localCipher.doFinal(paramString.getBytes("utf-8")));
}
private static String a(byte[] paramArrayOfByte)
{
String str1 = "";
int i = 0;
for (;;)
{
if (i >= paramArrayOfByte.length) {
return str1;
}
String str3 = Integer.toHexString(paramArrayOfByte[i] & 0xFF);
String str2 = str3;
if (str3.length() == 1) {
str2 = '0' + str3;
}
str1 = str1 + str2.toUpperCase();
i += 1;
}
}
From this we can determine:
- The algorithm is DESede (TripleDES) (applies DES encryption three times to each block).
- The mode is CBC (each plaintext block is XORed with the previous ciphertext block before encryption).
- Padding is PKCS5Padding (“The number of bytes to be padded equals to
8 - numberOfBytes(clearText) mod 8. So 1 to 8 bytes will be padded…”). - The initialization vector (IV) is
"01234567"—in this case it effectively acts like a salt.
The private static String a method appears to convert the byte array to an uppercase hex string.
Based on this, we can write a corresponding decryption routine:
public static String desDecode(String encryptText, String pk)//encryptText为密文,pk为私钥,通过跟踪到的代码可知为"neusofteducationplatform"
throws Exception
{
java.security.Key deskey = null;
DESedeKeySpec spec = new DESedeKeySpec(pk.getBytes());
SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede");
deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
IvParameterSpec ips = new IvParameterSpec("01234567".getBytes());
cipher.init(2, deskey, ips);
System.out.println(encryptText);
byte decryptData[] = cipher.doFinal(hexStringToBytes(encryptText));
return new String(decryptData, "utf-8");
}
public static String Bytes2HexString(byte b[])
{
String ret = "";
for(int i = 0; i < b.length; i++)
{
String hex = Integer.toHexString(b[i] & 0xff);
if(hex.length() == 1)
hex = (new StringBuilder(String.valueOf('0'))).append(hex).toString();
ret = (new StringBuilder(String.valueOf(ret))).append(hex.toUpperCase()).toString();
}
return ret;
}
Now take the ciphertext after action= from the earlier request:
0B38EC05B0815AFB59A1B6171DD8378702E08185784ADC47B213462CC176EBC626865CAE40CF998812E25558152958267F28B8BE59532A45283F2A274FA363CB685347955B330164CDEFA8228F011F4BB73C36BFF072DB9C
Decrypt it and you get:
method=checkUserLoginIFS&idNumber=20142110070202&UserPwd=011211&logonIP=10.0.8.1
At this point, you might ask: why is UserPwd=011211 different from USER_PASSWORD=?h?g?g+t.[=dR);: in the JSON above? The latter is the result after an additional string-encoding/encryption step.
Here is the corresponding decode code:
public static String Decode(String varCode)
{
String des = new String();
String strKey = new String();
if(varCode == null || varCode.length() == 0)
return "";
strKey = "zxcvbnm,./asdfghjkl;'qwertyuiop[]\1234567890-=` ZXCVBNM<>?:LKJHGFDSAQWERTYUIOP{}|+_)(*&^%$#@!~";
if(varCode.length() % 2 == 1)
varCode = (new StringBuilder(String.valueOf(varCode))).append("?").toString();
des = "";
int n;
for(n = 0; n <= varCode.length() / 2 - 1; n++) { char b = varCode.charAt(n * 2); int a = strKey.indexOf(varCode.charAt(n * 2 + 1)); des = (new StringBuilder(String.valueOf(des))).append((char)(b ^ a)).toString(); System.out.println(des); } n = des.indexOf('01'); if(n > 0)
return des.substring(0, n);
else
return des;
}
References
Java encryption (2) — DES data encryption algorithm (with IV)