夜雨聆风学习资料网

ARTICLE · 1150906

安卓逆向 -- 某软件的请求加密和响应解密流程

安卓逆向 -- 某软件的请求加密和响应解密流程

加密算法

既然我们要分析请求与响应那么我们必须要找到一个按钮或操作事件来触发请求, 那我们现在的目的很简单了

  • 打开目标软件
  • 打开任意一个网络监控软件(这里我使用的 Reqable ),注意过滤无效请求
  • 触发操作事件观察网络请求

如下图我找到抽奖领会员按钮被点击后必然会发送一个请求

我们已经找到请求被触发的时机,接下来我们去分析它的反编译后的代码查看它到底是怎么进行加密的

1.在jadx中搜索关键词 -> 找到资源ID -> 查找引用位置 -> 定位到了按钮的定义 -> 查看按钮被点击的回调 -> 发现在回调中会创建新的窗体 -> 查看该窗体的 onCreate 函数。如下:

 复制代码 隐藏代码
@Override// com.lvcha.main.BaseActivity
publicvoidonCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.setContentView(0x7F0A0011);
this.o = (TextView)this.findViewById(0x7F080135);
this.p = (ImageView)this.findViewById(0x7F080141);
this.q = (TextView)this.findViewById(0x7F080140);
this.r = (TextView)this.findViewById(0x7F08013C);
this.s = (TextView)this.findViewById(0x7F080142);
this.t = (ImageView)this.findViewById(0x7F08013E);
this.u = (Button)this.findViewById(0x7F08013D);
this.v = (Button)this.findViewById(0x7F080134);
if(vs1.F().Z()) {
this.u.setEnabled(false);
this.u.setText(0x7F0D0020);
    }
else {
this.u.setEnabled(true);
this.u.setText(0x7F0D002F);
    }

this.o.setText(String.valueOf(vs1.F().J()));
this.u.setOnClickListener((View view0) -> {
this.getClass();
this.startActivityForResult(newIntent(this, LvchaLoginActivity.class).putExtra(hl1.a("vRBNy4s=\n", "0H8prueTtEU=\n"), 2), 1);
    });
this.v.setOnClickListener((View view0) -> {
this.getClass();
this.startActivityForResult(newIntent(this, LvchaChargeActivity.class), 1);
    });
this.p.setOnClickListener((View view0) -> {
if(this.z != null && (this.z.isRunning())) {
return;
        }

if(vs1.F().S() < 1) {
this.y(0x7F0D015F, this.getString(0x7F0D0160));
return;
        }

ObjectAnimatorobjectAnimator0= ObjectAnimator.ofFloat(this.t, hl1.a("h7nSo7Yz3Zs=\n", "9damwsJasvU=\n"), newfloat[]{0.0f, 360.0f});
this.z = objectAnimator0;
        objectAnimator0.setRepeatCount(-1);
this.z.setDuration(1000L);
this.z.setInterpolator(newLinearInterpolator());
this.z.start();
this.w();
    });
this.x();
this.v();
}

我们可以发现代码前面都是对新窗体的页面控件进行设置,而 this.x(); 里面是进行设置监听器,做页面动画使用的,通过观察 this.v(); 发现就是它在发送请求。

ya00.N 方法是响应的回调, ya00.u 是进行发送请求。遇到这样的混淆可以通过AI让它协助分析。

 复制代码 隐藏代码
publicfinalvoidv() {
if((vs1.F().Z()) && (this.u.isEnabled())) {
this.u.setEnabled(false);
this.u.setText(0x7F0D0020);
    }

ya0ya00=newya0();
    ya00.N((w w0, int v, Object object0) -> {
this.getClass();
if(v != 0) {
if(v != 5) {
return;
            }

try {
                it1.J(((String)object0), newcom.lvcha.main.activity.LvchaLotteryActivity.b(this));
            }
catch(Throwable throwable0) {
                throwable0.printStackTrace();
            }

return;
        }

        q.O(() -> Toast.makeText(lvchaLotteryActivity0, lvchaLotteryActivity0.getString(0x7F0D00D9), 0).show());
    });
    ya00.u(vq1.getLotteryCountURL(), vq1.getRequestParams(newArrayMap()));

publicclasscom.lvcha.main.activity.LvchaLotteryActivity.b implementso01 {
@Override// o01
publicvoida(int v, String s) {
            q.O(() -> Toast.makeText(LvchaLotteryActivity.this, s, 0).show());
        }

@Override// o01
publicvoidb(JSONObject jSONObject0) {
            vs1.F().E0(jSONObject0.optInt(hl1.a("MZY8VGSTkFA+lj1OdQ==\n", "XflIIAHh6Q8=\n"), 0));  // lottery_count
            LvchaLotteryActivity.this.w = jSONObject0.optInt(hl1.a("0Z3FL5aIJm7Th9w5log=\n", "vfKxW/P6XzE=\n"), 0);  // lottery_number
            LvchaLotteryActivity.this.x = jSONObject0.optInt(hl1.a("SnWfhwcd5npSc4aW\n", "Jhrr82JvnyU=\n"), 0);  // lottery_time
            q.O(() -> {
                LvchaLotteryActivity.this.o.setText(String.valueOf(vs1.F().S()));
                LvchaLotteryActivity.this.r.setText(String.valueOf(LvchaLotteryActivity.this.w));
                LvchaLotteryActivity.this.s.setText(String.valueOf(LvchaLotteryActivity.this.x));
            });
        }
    }

}

我们找到了请求代码接下来就可以分析它是怎么进行的发送请求

ya00.u 第一个参数我们点进去观察,发现它是一个字符串,这个字符串刚好和我们的请求路径一样,可以推测这是在进行设置请求路径

 复制代码 隐藏代码
publicstatic String getLotteryCountURL() {
return vq1.k() + hl1.a("7levzFim01iuVeiGTbrke7RP6A==\n", "wSGc4z/DpxQ=\n");  // a方法返回值:  /v3/getLotteryCount
}

ya00.u 第二个参数我们点进去观察,发现它是请求参数的封装Map,特别是 content 请求的加密数据

 复制代码 隐藏代码
// 获取请求参数 进行签名
publicstatic Map getRequestParams(Map map0) {
if(map0 == null) {
returnnull;
    }

    vs1.F().getGlobalParams(map0);
    uv.getSignatureParams(q.getApplication(), map0);
HashMaphashMap0=newHashMap();
    hashMap0.put(hl1.a("9Zx5G79o6w==\n", "lvMXb9oGn/g=\n"), Signature.a(map0));  // a方法返回值:  content
if(!it1.X(vs1.F().T())) {
        hashMap0.put(hl1.a("tEekhZg=\n", "wCjP4PaUErg=\n"), vs1.F().T());
    }

    hashMap0.put(hl1.a("nQ==\n", "6/Fbr5mpncM=\n"), hl1.a("OUI=\n", "C3BtcjmYpSw=\n"));
return hashMap0;
}

进入 Signature.a 方法进行观察,发现它对传入的Map进行处理后调用了一个native方法(public static native String cs)

 复制代码 隐藏代码
import hl1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Map;

publicabstractclassSignature {
static {
        System.loadLibrary(hl1.a("v1OS586aota6U5Q=\n", "0yXxj6/0w6I=\n"));
    }

publicstatic String a(Map map0) {
ArrayListarrayList0=newArrayList();
for(Object object0: map0.entrySet()) {
            Map.Entrymap$Entry0= (Map.Entry)object0;
if(map$Entry0.getValue() == null || ("".equals(map$Entry0.getValue().toString()))) {
continue;
            }

            arrayList0.add(((String)map$Entry0.getKey()) + hl1.a("Tg==\n", "c3Rmtq35VCY=\n") + map$Entry0.getValue() + hl1.a("eQ==\n", "X/1KX2lL9eA=\n"));  // 处理参数 k=v&
        }

        String[] arr_s = (String[])arrayList0.toArray(newString[0]);
return Signature.cs(((String[])Arrays.copyOf(arr_s, arr_s.length + 1)));
    }

publicstaticvoidb() {
    }

publicstaticnative String cs(String[] arg0) {
    }

publicstaticnative String d(byte[] arg0) {
    }

publicstaticnative String ga() {
    }

// w.w 真正执行了这个代码
publicstaticnativebyte[] gc(String arg0) {
    }
}

我们对两个方法进行 hook 观察它们到底在做什么,通过观察确定请求所需要的加密数据就是在 cs 内部完成的。

 复制代码 隐藏代码
类名:com.lvcha.main.util.Signature
日志名称:a(Map)

参数1
参数类型:android.util.ArrayMap
参数值:{device=a3a8d7c25cdf8bd7, p1=101001, p2=6, p3=2, p4=MI+CC9+Pro, p5=3, p6=30, p7=com.abjlvcha.main, p8=22, p9=0adddcd0-0f6f-4df6-a2e1-b5c8efd4b40f, p10=47, p11=zh, p12=e8704e350d52e68456aeb63f85b17fa6, p13=5775940, uid=dcc04ace3b68479a96d73a29bbc3c726, token=7z+9RIFgc8IH//W2ojpyBbrU9Ah2EO/kGxgvNmh2fp0k2akDC9ATTqPsFSkFCfTOZG0VoqE3wixngkmHVjOnnQYWy+aX066t6HfOMGOGV/IPWWG8FJSVGjaViTeITLHLG0EzSX+AuwSUjH1xG7E0NyIgLXjUKdfxmY++8pXvlnXKMToJy2PqrvMNFbB7Ov24q8YxlRDNlJDiIYthVwUHmrqNnCYxBMfXjGgOlYaNBf1QIuDEkLhyAMEGhk09SJK/6G0J7rlt16xJ8rYv5P/t17Z2+xLi90O3O3tx6BSpvuKRbQg/fJ86r5FMuwLoLYha3bW7LMLLy3Wt99yLvNmP2IWy4XsBhenrD3w61XZl79aNi0R0dv09kYdvjrJXup1sfKPwm7H0is0Zrg6yj3rdaR17D9vRjTECGOEnktU7vEdxvnnL2fHM5q1rDv8NHldYsDsnUOZOOFf7QesSmMh/ylsfFzEXdv0RrSa2z71vfbcRYTP0iweQa4yJzg9h69lvIYJ8dD+UBbiAW+CvEkY8uz29RoxMnr5dKreJ7UaoaLrJFyusaOAtgPSqIWx6/kivUKGdnA==, platform=1}

返回结果类型:java.lang.String
返回结果值:AoxCzVP3zpaAiTLw0ih0/HfJkYMP3R6pcjoAvsKDvp8+gO2nIgNO/S3bTunI/5KW/Qoax+H0Cx0fvfhK0zzG4Rj0CqtMMa9qsTUIrqUs914IHL1ndhgua7+FOQWNWUUE0nIQ1T+7BFdX0+5jHdgDO0MUqREIVnnG8GXoOt8zHA5zWSjpobKzrqAp21UITdEUpXcMHefCBeIk+iJFW312taKTIxHUHLnENhQARiGorY/dpBxa/9v2CEWApufspR1eppsdG3cNIyJjcgqYF0omo+wcodBW9jt92HaR+gBd3qxwbjvRocJkboKMJ/Hb/aH+s7BlXELd+XBoMAS/7EHFwXT+Qx/2jTswN3+0j+MRtFmgPzcAhwc17seuHZXKrxsG7mz+S9MT2QJGoTHTkjcBmNmESD08PeS4bWZ7xC0DrgyhoCp5D4AJzW77lyjRUY9VoA2foiWCfaIZ+ugvsqBtGsPbP37V5j9hL5i6jqZQUjszm4xHqQhVEgB04Xe1klEwhAakRXrHVNiRV7fanNG4zNgxYBEwUYWogCbv1kGNM+Z2WXM8DA7OL2eu8WADgBEjZZHJC1nm9KWIaCrZbNuKbtFnM1dXv9KMVMwXMECPW2hAqrrT5gcCHFlMYpWYJEeIqimQYYpDXnOdL8UH9BEFTATG3+Ut7d5pSK43fLd5K8Vcuxb2/7swDU/Fd8edB4LIXMaiGyU/9MCaEzfaDlnc1k1oAGIcoMi/Bl4PsV0++XEwsekUC47olxwusp9KKP4aSxME8i66Vd8eLWjwmOkwu+z/JDTBrElzFZ7ttLomU5ZCuNPtv8C4vOEjHBIfk605t7wP0GXS6jK5RfXi34nD5e8M6tajKP2peMfz8qW38czHe+25cCfNtUJBLs1fJCIvga7ZX44lJzE4RuUUtOS5KsDMhz9nRyecBLekmPumC0ehSmyPDLRTtNHd6syYHMrdE3sryUuZXX7fMA0jpw3ovckxETG9yqjR6CqEsS4I7U6IbIwOMipFI8UIX+Z78qZUC6GuZZkBgiZqm2OXW3cV2mEu8rEHQapw5pstWTS5KOHRnfKHxIxOySgWh0Vq9GobODxR/5Hn7DrdFQTFh4GYiGVMbKUSc5WFBnqi/ISq3WcXJdmnm/JR1VFmSbEkJrjXk+6t6xajtI+VaK2u8TNV

------------------------------------------------------------------------------------------------

类名:com.lvcha.main.util.Signature
日志名称:cs(String;)

参数1
参数类型:[Ljava.lang.String;
参数值:{device=a3a8d7c25cdf8bd7&,p1=101001&,p2=6&,p3=2&,p4=MI+CC9+Pro&,p5=3&,p6=30&,p7=com.abjlvcha.main&,p8=22&,p9=0adddcd0-0f6f-4df6-a2e1-b5c8efd4b40f&,p10=47&,p11=zh&,p12=e8704e350d52e68456aeb63f85b17fa6&,p13=5775940&,uid=dcc04ace3b68479a96d73a29bbc3c726&,token=7z+9RIFgc8IH//W2ojpyBbrU9Ah2EO/kGxgvNmh2fp0k2akDC9ATTqPsFSkFCfTOZG0VoqE3wixngkmHVjOnnQYWy+aX066t6HfOMGOGV/IPWWG8FJSVGjaViTeITLHLG0EzSX+AuwSUjH1xG7E0NyIgLXjUKdfxmY++8pXvlnXKMToJy2PqrvMNFbB7Ov24q8YxlRDNlJDiIYthVwUHmrqNnCYxBMfXjGgOlYaNBf1QIuDEkLhyAMEGhk09SJK/6G0J7rlt16xJ8rYv5P/t17Z2+xLi90O3O3tx6BSpvuKRbQg/fJ86r5FMuwLoLYha3bW7LMLLy3Wt99yLvNmP2IWy4XsBhenrD3w61XZl79aNi0R0dv09kYdvjrJXup1sfKPwm7H0is0Zrg6yj3rdaR17D9vRjTECGOEnktU7vEdxvnnL2fHM5q1rDv8NHldYsDsnUOZOOFf7QesSmMh/ylsfFzEXdv0RrSa2z71vfbcRYTP0iweQa4yJzg9h69lvIYJ8dD+UBbiAW+CvEkY8uz29RoxMnr5dKreJ7UaoaLrJFyusaOAtgPSqIWx6/kivUKGdnA==&,platform=1&,null}

返回结果类型:java.lang.String
返回结果值:AoxCzVP3zpaAiTLw0ih0/HfJkYMP3R6pcjoAvsKDvp8+gO2nIgNO/S3bTunI/5KW/Qoax+H0Cx0fvfhK0zzG4Rj0CqtMMa9qsTUIrqUs914IHL1ndhgua7+FOQWNWUUE0nIQ1T+7BFdX0+5jHdgDO0MUqREIVnnG8GXoOt8zHA5zWSjpobKzrqAp21UITdEUpXcMHefCBeIk+iJFW312taKTIxHUHLnENhQARiGorY/dpBxa/9v2CEWApufspR1eppsdG3cNIyJjcgqYF0omo+wcodBW9jt92HaR+gBd3qxwbjvRocJkboKMJ/Hb/aH+s7BlXELd+XBoMAS/7EHFwXT+Qx/2jTswN3+0j+MRtFmgPzcAhwc17seuHZXKrxsG7mz+S9MT2QJGoTHTkjcBmNmESD08PeS4bWZ7xC0DrgyhoCp5D4AJzW77lyjRUY9VoA2foiWCfaIZ+ugvsqBtGsPbP37V5j9hL5i6jqZQUjszm4xHqQhVEgB04Xe1klEwhAakRXrHVNiRV7fanNG4zNgxYBEwUYWogCbv1kGNM+Z2WXM8DA7OL2eu8WADgBEjZZHJC1nm9KWIaCrZbNuKbtFnM1dXv9KMVMwXMECPW2hAqrrT5gcCHFlMYpWYJEeIqimQYYpDXnOdL8UH9BEFTATG3+Ut7d5pSK43fLd5K8Vcuxb2/7swDU/Fd8edB4LIXMaiGyU/9MCaEzfaDlnc1k1oAGIcoMi/Bl4PsV0++XEwsekUC47olxwusp9KKP4aSxME8i66Vd8eLWjwmOkwu+z/JDTBrElzFZ7ttLomU5ZCuNPtv8C4vOEjHBIfk605t7wP0GXS6jK5RfXi34nD5e8M6tajKP2peMfz8qW38czHe+25cCfNtUJBLs1fJCIvga7ZX44lJzE4RuUUtOS5KsDMhz9nRyecBLekmPumC0ehSmyPDLRTtNHd6syYHMrdE3sryUuZXX7fMA0jpw3ovckxETG9yqjR6CqEsS4I7U6IbIwOMipFI8UIX+Z78qZUC6GuZZkBgiZqm2OXW3cV2mEu8rEHQapw5pstWTS5KOHRnfKHxIxOySgWh0Vq9GobODxR/5Hn7DrdFQTFh4GYiGVMbKUSc5WFBnqi/ISq3WcXJdmnm/JR1VFmSbEkJrjXk+6t6xajtI+VaK2u8TNV

我们需要在IDA中查看 cs 代码

 复制代码 隐藏代码
__int64 __fastcall Java_com_lvcha_main_util_Signature_cs(JNIEnv *env, jclass classz, jobjectArray inputArray)
{
unsigned __int64 StatusReg; // x25
int v6; // w22
  jstring v7; // x26
int v8; // w5
int v9; // w6
int v10; // w7
unsignedint v11; // w23
  jobject v12; // x24
  __int64 (__fastcall *s)(int, int, int, int, int, int, int, int, int, int, int, int, __int64, int, void *); // x0
unsigned __int64 *v14; // x25
  __int64 (__fastcall *src)(int, int, int, int, int, int, int, int, int, int, int, int, __int64, int, void *); // x26
size_t n0x17; // x0
size_t n; // x27
void *dest; // x28
  __int64 v19; // x0
unsigned __int64 n26; // x20
  JNIEnv v21; // x8
char *v22; // x0
char *v23; // x1
unsigned __int64 v25; // x8
int v26; // w3
unsigned __int8 *v27; // x20
unsigned __int8 *v28; // x21
unsigned __int64 v29; // x8
void *src_1; // x1
size_t n_1; // x2
unsignedint _Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char_1; // w9
unsignedint v33; // w8
  __int64 v34; // x1
  jbyteArray v35; // x21
const jbyte *v36; // x4
unsigned __int64 v37; // x3
void *v38; // x23
void *v39; // x24
  __int64 v40; // x22
unsigned __int64 *v41; // x19
unsigned __int64 *v42; // x8
void *v43; // x0
unsigned __int64 *v44; // x20
char v45; // t1
  __int64 v46; // [xsp+0h] [xbp-90h]
  jstring v47; // [xsp+8h] [xbp-88h]
void *StatusReg_1; // [xsp+10h] [xbp-80h]
  __int64 (__fastcall *p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char)(int, int, int, int, int, int, int, int, int, int, int, int, __int64, int, void *); // [xsp+18h] [xbp-78h] BYREF
  __int64 v50; // [xsp+20h] [xbp-70h]
void *v51; // [xsp+28h] [xbp-68h]
void *v52; // [xsp+30h] [xbp-60h] BYREF
unsigned __int64 *v53; // [xsp+38h] [xbp-58h]
unsigned __int64 v54; // [xsp+40h] [xbp-50h]
  _BYTE v55[64]; // [xsp+48h] [xbp-48h] BYREF
  __int64 v56; // [xsp+88h] [xbp-8h]

  StatusReg = _ReadStatusReg(TPIDR_EL0);
  v56 = *(_QWORD *)(StatusReg + 40);
if ( byte_EAA58 != 1 )
return0;
  v6 = (*env)->GetArrayLength(env, inputArray);
  sub_69208((__int64)v55, 64, 64, "p14=%ld&", qword_EA7F0);// 这里 qword_EA7F0 的值应该是apk大小
  v7 = (*env)->NewStringUTF(env, v55);
  (*env)->SetObjectArrayElement(env, inputArray, v6 - 1, v7);
  v52 = 0;
  v53 = 0;
  v54 = 0;
if ( v6 >= 1 )
  {
    v11 = 0;
    v47 = v7;
    StatusReg_1 = (void *)StatusReg;
while ( 1 )
    {
      v12 = (*env)->GetObjectArrayElement(env, inputArray, v11);
      s = (__int64 (__fastcall *)(int, int, int, int, int, int, int, int, int, int, int, int, __int64, int, void *))(*env)->GetStringUTFChars(env, v12, 0);
      v14 = v53;
      src = s;
      p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char = s;
if ( (unsigned __int64)v53 < v54 )
break;
      v19 = std::vector<std::string>::__emplace_back_slow_path<charconst*&>(
              &v52,
              &p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char);
LABEL_16:
      v21 = *env;
      v53 = (unsigned __int64 *)v19;
      v21->ReleaseStringUTFChars(
        env,
        v12,
        (constchar *)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char);
      (*env)->DeleteLocalRef(env, v12);
if ( v6 == ++v11 )
      {
        v22 = (char *)v52;
        v23 = (char *)v53;
        v7 = v47;
goto LABEL_20;
      }
    }
    n0x17 = strlen((constchar *)s);
if ( n0x17 >= 0xFFFFFFFFFFFFFFF8LL )
      sub_6ADA0(v14);
    n = n0x17;
if ( n0x17 >= 0x17 )
    {
if ( (n0x17 | 7) == 0x17 )
        n26 = 26;
else
        n26 = (n0x17 | 7) + 1;
      dest = (void *)operator new(n26);
      v14[1] = n;
      v14[2] = (unsigned __int64)dest;
      *v14 = n26 | 1;
    }
else
    {
      *(_BYTE *)v14 = 2 * n0x17;
      dest = (char *)v14 + 1;
if ( !n0x17 )
goto LABEL_15;
    }
    memmove(dest, src, n);
LABEL_15:
    *((_BYTE *)dest + n) = 0;
    v19 = (__int64)(v14 + 3);
goto LABEL_16;
  }
  v23 = 0;
  v22 = 0;
LABEL_20:
  v25 = 126 - 2 * __clz(0xAAAAAAAAAAAAAAABLL * ((v23 - v22) >> 3));
if ( v23 == v22 )
    v26 = 0;
else
    v26 = v25;
  p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char = caseInsensitiveCompare;
std::__introsort<std::_ClassicAlgPolicy,bool (*&)(std::stringconst&,std::stringconst&),std::string*,false>(
    (int)v22,
    (int)v23,
    (int)&p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char,
    v26,
1,
    v8,
    v9,
    v10,
    v46,
    (int)v47,
    StatusReg_1);
  v28 = (unsigned __int8 *)v52;
  v27 = (unsigned __int8 *)v53;
  p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char = 0;
  v50 = 0;
  v51 = 0;
if ( v52 == v53 )
  {
    v33 = 0;
    _Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char_1 = 0;
  }
else
  {
do
    {
      v29 = *v28;
if ( (v29 & 1) != 0 )
        src_1 = (void *)*((_QWORD *)v28 + 2);
else
        src_1 = v28 + 1;
if ( (v29 & 1) != 0 )
        n_1 = *((_QWORD *)v28 + 1);
else
        n_1 = v29 >> 1;
std::string::append((int)&p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char, src_1, n_1);
      v28 += 24;
    }
while ( v28 != v27 );
    _Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char_1 = (unsigned __int8)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char;
    v33 = v50;
  }
if ( (_Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char_1 & 1) != 0 )
    v34 = v33;
else
    v34 = _Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char_1 >> 1;
  v35 = (*env)->NewByteArray(env, v34);
if ( ((unsigned __int8)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char & 1) != 0 )
    v36 = (const jbyte *)v51;
else
    v36 = (char *)&p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char + 1;
if ( ((unsigned __int8)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char & 1) != 0 )
    v37 = (unsignedint)v50;
else
    v37 = (unsigned __int64)(unsigned __int8)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char >> 1;
  (*env)->SetByteArrayRegion(env, v35, 0, v37, v36);
  v38 = (void *)_JNIEnv::CallStaticObjectMethod(env, qword_EAA60, qword_EAA68);
  v39 = (void *)_JNIEnv::CallStaticObjectMethod(env, qword_EAA60, qword_EAA78);
  v40 = _JNIEnv::CallStaticObjectMethod(env, qword_EAA60, qword_EAA80);
  (*env)->DeleteLocalRef(env, v39);
if ( v38 )
    (*env)->DeleteLocalRef(env, v38);
  (*env)->DeleteLocalRef(env, v7);
  (*env)->DeleteLocalRef(env, v35);
if ( ((unsigned __int8)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char & 1) != 0 )
    operator delete(
      v51,
      (unsigned __int64)p__Z22caseInsensitiveCompareRKNSt6__ndk112basic_stringIcNS_11char & 0xFFFFFFFFFFFFFFFELL)
;
  v41 = (unsigned __int64 *)v52;
if ( v52 )
  {
    v42 = v53;
    v43 = v52;
if ( v52 != v53 )
    {
      v44 = v53;
do
      {
        v45 = *((_BYTE *)v44 - 24);
        v44 -= 3;
if ( (v45 & 1) != 0 )
          operator delete((void *)*(v42 - 1), *(v42 - 3) & 0xFFFFFFFFFFFFFFFELL);
        v42 = v44;
      }
while ( v44 != v41 );
      v43 = v52;
    }
    v53 = v41;
    operator delete(v43, v54 - (_QWORD)v43);
  }
return v40;
}

我让AI还原的代码

 复制代码 隐藏代码
#include<jni.h>
#include<vector>
#include<string>
#include<algorithm>
#include<cstdio>

// 全局变量
externbool byte_EAA58 = 1;
externint64_t qword_EA7F0 = 5,775,940; // apk大小
extern jclass qword_EAA60;    // Ru3 类
extern jmethodID qword_EAA68; // 方法: a()[B
extern jmethodID qword_EAA78; // 方法: b([B[B)[B
extern jmethodID qword_EAA80; // 方法: a([B)Ljava/lang/String;

// 不区分大小写比较函数
boolcaseInsensitiveCompare(conststd::string &a, conststd::string &b);

jstring Java_com_lvcha_main_util_Signature_cs(JNIEnv *env, jclass clazz, jobjectArray inputArray)
{
if (!byte_EAA58)
  {
// JNI_Load 中进行的赋值
return nullptr;
  }

  jsize len = env->GetArrayLength(inputArray);
if (len <= 0)
  {
return nullptr; // 输入数组不应为空
  }

// 构造参数字符串 "p14=0&" 并添加到数组末尾
char buffer[64];
snprintf(buffer, sizeof(buffer), "p14=%ld&", qword_EA7F0);
  jstring newElement = env->NewStringUTF(buffer);
  env->SetObjectArrayElement(inputArray, len - 1, newElement);

// 将 Java 字符串数组转换为 std::vector<std::string>
std::vector<std::string> vec;
  vec.reserve(len);
for (jsize i = 0; i < len; ++i)
  {
    jstring strObj = (jstring)env->GetObjectArrayElement(inputArray, i);
constchar *utfStr = env->GetStringUTFChars(strObj, nullptr);
    vec.push_back(utfStr);
    env->ReleaseStringUTFChars(strObj, utfStr);
    env->DeleteLocalRef(strObj);
  }

// 不区分大小写排序
std::sort(vec.begin(), vec.end(), caseInsensitiveCompare);

// 拼接所有字符串
std::string concatenated;
for (constauto &s : vec)
  {
    concatenated.append(s);
  }

// 将拼接结果转换为 Java byte[]
  jbyteArray byteArray = env->NewByteArray(concatenated.size());
  env->SetByteArrayRegion(byteArray, 0, concatenated.size(), reinterpret_cast<const jbyte *>(concatenated.data()));

// 获取 Ru3 类
  jclass clazzRu3 = env->FindClass("u3/Ru3");

// 获取方法 ID
  jmethodID methodId_a = env->GetStaticMethodID(clazz, "a", "()[B;");
  jmethodID methodId_b = env->GetStaticMethodID(clazz, "b", "([B;[B;)[B;");
  jmethodID methodId_a2 = env->GetStaticMethodID(clazz, "a", "([B;)Ljava/lang/String;");

// 调用 Java 方法进行加密处理
  jbyteArray key1 = (jbyteArray)env->CallStaticObjectMethod(clazzRu3, methodId_a);
  jbyteArray encrypted = (jbyteArray)env->CallStaticObjectMethod(clazzRu3, methodId_b, byteArray, key1);
  jstring result = (jstring)env->CallStaticObjectMethod(clazzRu3, methodId_a2, encrypted);

// 清理局部引用
  env->DeleteLocalRef(newElement);
  env->DeleteLocalRef(byteArray);
  env->DeleteLocalRef(key1);
  env->DeleteLocalRef(encrypted);

return result;
}

其中 qword_EAA68 qword_EAA78 qword_EAA80 三个全局变量可以发现都是在 JNI_OnLoad 中被赋值的,我们可以通过 unidbg 加载 JNI_OnLoad 并查看它打印的日志

 复制代码 隐藏代码
publicstaticvoidmain(String[] args) {
// 创建 Signature 对象
Signaturesignature=newSignature();
}

/**
 * 构造方法,初始化AndroidEmulator模拟器和VM虚拟机
 */

protectedSignature() {
    emulator = AndroidEmulatorBuilder
            .for64Bit()  // 32位模拟器
            .setProcessName("com.lvcha.main")  // 设置进程名
// .addBackendFactory(new DynarmicFactory(true))  // 添加Dynarmic后端工厂
            .build();
// 获取内存对象
Memorymemory= emulator.getMemory();
// 设置库解析器
LibraryResolverresolver=newAndroidResolver(23);
    memory.setLibraryResolver(resolver);

// 开启GDB调试(可以设置断点)
// Debugger debugger = emulator.attach(DebuggerType.CONSOLE);
// debugger.addBreakPoint(null, 0x6A2E8);

// 创建Dalvik虚拟机
    vm = emulator.createDalvikVM();
    vm.setJni(this);
// 设置Dalvik虚拟机为verbose模式
    vm.setVerbose(true);
// 加载本地库,参数1为库文件路径,参数2是否执行JNI_OnLoad方法
DalvikModuledm= vm.loadLibrary(
newFile("unidbg-android/src/test/resources/com/abjlvcha/main/liblvchanative.so"),
false
    );

// 先获取so库模块
Modulemodule= emulator.getMemory().findModule("liblvchanative.so");
// 开启 CONSOLE 调试(可以设置断点)
Debuggerdebugger= emulator.attach(DebuggerType.CONSOLE);
// JNI_OnLoad qword_EA7F0 = v21;
// debugger.addBreakPoint(module, 0x69BB8);
// 静态变量 qword_EAA60 的赋值
// debugger.addBreakPoint(module, 0x69808);
// 主动调用JNI_OnLoad方法
    dm.callJNI_OnLoad(emulator);
}

打印日志的顺序可以知道  qword_EAA68 qword_EAA78 qword_EAA80 三个全局变量的值

 复制代码 隐藏代码
JNIEnv->FindClass(u3/Ru3) was called from RX@0x120697b4[liblvchanative.so]0x697b4
JNIEnv->NewGlobalRef(class u3/Ru3) was called from RX@0x120697c8[liblvchanative.so]0x697c8
JNIEnv->GetStaticMethodID(u3/Ru3.a([B)Ljava/lang/String;) => 0xd4edb7d5 was called from RX@0x120697f8[liblvchanative.so]0x697f8
JNIEnv->GetStaticMethodID(u3/Ru3.a(Ljava/lang/String;)[B) => 0xb0d016f was called from RX@0x12069820[liblvchanative.so]0x69820
JNIEnv->GetStaticMethodID(u3/Ru3.b([B[B)[B) => 0xc740e7ec was called from RX@0x12069850[liblvchanative.so]0x69850
JNIEnv->GetStaticMethodID(u3/Ru3.c([B[B)Ljava/lang/String;) => 0x15f12ab0 was called from RX@0x1206987c[liblvchanative.so]0x6987c
JNIEnv->GetStaticMethodID(u3/Ru3.a([B[B)[B) => 0x32fc78eb was called from RX@0x120698a0[liblvchanative.so]0x698a0
JNIEnv->GetStaticMethodID(u3/Ru3.a()[B) => 0x509a40f9 was called from RX@0x120698c8[liblvchanative.so]0x698c8

后面我们只需要分析 cs native 中调用到的Java三个函数 就可以知道它的加密逻辑了

加密Java代码如下:

 复制代码 隐藏代码
package com.abjlvcha.main.util;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;

publicclassLvchaSecurityProvider {

// 自定义Base64编码表
privatestaticfinalchar[] BASE64_TABLE = newchar[64];

// 可手动设置的IV(初始化向量),用于调试或固定场景
publicstaticStringuse_iv=null;

// 静态初始化块:初始化Base64编码表
static {
        initBase64Table();
    }

// 初始化自定义Base64编码表
privatestaticvoidinitBase64Table() {
intv=0;
int v5;
// 添加A-Z (26个大写字母)
for (v5 = 0; v5 <= 25; ++v5) {
            BASE64_TABLE[v5] = (char) (v5 + 65);
        }

intv6=26;
int v7;
// 添加a-z (26个小写字母)
for (v7 = 0; v6 <= 51; ++v7) {
            BASE64_TABLE[v6] = (char) (v7 + 97);
            ++v6;
        }

intv8=52;
// 添加0-9 (10个数字)
while (v8 <= 61) {
            BASE64_TABLE[v8] = (char) (v + 0x30);
            ++v8;
            ++v;
        }

// 添加Base64最后两个字符
        BASE64_TABLE[62] = '+';
        BASE64_TABLE[0x3F] = '/';
    }

/**
     * 生成签名
     * @Param inputArray 输入参数数组
     * @param uid 用户ID,用作加密密钥
     * @return Base64编码的签名字符串
     */

publicstatic String generateSignature(String[] inputArray, String uid) {
try {
// 将数组转换为List,便于操作
            List<String> params = newArrayList<>();
            Collections.addAll(params, inputArray);

// 修改最后一个参数
            params.set(params.size() - 1, "p14=5775940&");

// 对参数进行不区分大小写的字典序排序
            Collections.sort(params, newComparator<String>() {
@Override
publicintcompare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
                }
            });

// 拼接所有参数
StringBuilderconcatenated=newStringBuilder();
for (String s : params) {
                concatenated.append(s);
            }

// 将字符串转换为字节数组
byte[] data = concatenated.toString().getBytes(StandardCharsets.UTF_8);
// 使用UID作为密钥
byte[] key = uid.getBytes(StandardCharsets.UTF_8);

// 使用AES-GCM加密
byte[] encrypted = encryptAesGcm(data, key);

// 返回自定义Base64编码结果
return customBase64Encode(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
returnnull;
        }
    }

/**
     * 使用AES-GCM加密数据
     * @param data 待加密数据
     * @param key 密钥
     * @return 加密后的字节数组
     */

privatestaticbyte[] encryptAesGcm(byte[] data, byte[] key) throws Exception {
// 生成12字节的随机IV
byte[] iv = newbyte[12];
newSecureRandom().nextBytes(iv);

// 如果手动设置了IV,则使用自定义IV
if (use_iv != null && !use_iv.trim().isEmpty())
            iv = Base64.getDecoder().decode(use_iv);

// 获取AES/GCM/NoPadding加密实例
Ciphercipher= Cipher.getInstance("AES/GCM/NoPadding");
// 创建密钥规范
SecretKeySpecsecretKeySpec=newSecretKeySpec(key, "AES");
// 创建GCM参数规范(128位认证标签长度)
GCMParameterSpecgcmParameterSpec=newGCMParameterSpec(128, iv);

// 初始化加密器
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);

// 执行加密
byte[] encrypted = cipher.doFinal(data);

// 构建结果:版本字节(2) + IV + 加密数据
byte[] result = newbyte[1 + iv.length + encrypted.length];
        result[0] = 2;  // 版本标识
        System.arraycopy(iv, 0, result, 1, iv.length);
        System.arraycopy(encrypted, 0, result, 1 + iv.length, encrypted.length);

return result;
    }

/**
     * 自定义Base64编码实现
     * @param data 待编码数据
     * @return Base64编码字符串
     */

privatestatic String customBase64Encode(byte[] data) {
if (data == null) {
returnnull;
        }

inttotalBits= data.length * 8;
if (totalBits == 0) {
return"";
        }

intremainderBits= totalBits % 24;
intfullGroups= totalBits / 24;
char[] output = newchar[(remainderBits == 0 ? fullGroups : fullGroups + 1) * 4];
intoutIndex=0;
intinIndex=0;
intgroupIndex=0;

// 处理完整的3字节组
while (groupIndex < fullGroups) {
byteb0= data[inIndex];
intv6= inIndex + 2;
byteb1= data[inIndex + 1];
            inIndex += 3;
byteb2= data[v6];

// 第一个字符:取b0的高6位
int idx0;
if ((b0 & 0xFFFFFF80) == 0) {
                idx0 = b0 >> 2;
            } else {
                idx0 = (b0 >> 2) ^ 0xC0;
            }
            output[outIndex] = BASE64_TABLE[(byte) idx0];

// 第二个字符:取b0的低2位和b1的高4位
int idx1_part1;
if ((b1 & 0xFFFFFF80) == 0) {
                idx1_part1 = b1 >> 4;
            } else {
                idx1_part1 = (b1 >> 4) ^ 0xF0;
            }
intidx1= idx1_part1 | ((b0 & 3) << 4);
            output[outIndex + 1] = BASE64_TABLE[(byte) idx1];

intv7= outIndex + 3;
// 第三个字符:取b1的低4位和b2的高2位
intidx2_part1= (b1 & 15) << 2;
int idx2_part2;
if ((b2 & 0xFFFFFF80) == 0) {
                idx2_part2 = b2 >> 6;
            } else {
                idx2_part2 = (b2 >> 6) ^ 0xFC;
            }
intidx2= idx2_part1 | idx2_part2;
            output[outIndex + 2] = BASE64_TABLE[(byte) idx2];

            outIndex += 4;
// 第四个字符:取b2的低6位
            output[v7] = BASE64_TABLE[b2 & 0x3F];
            groupIndex++;
        }

// 处理剩余字节
switch (remainderBits) {
case8: {
break;
            }
case16: {
byteb3= data[inIndex];
byteb4= data[inIndex + 1];

// 第一个字符
int idx0;
if ((b3 & 0xFFFFFF80) == 0) {
                    idx0 = b3 >> 2;
                } else {
                    idx0 = (b3 >> 2) ^ 0xC0;
                }
                output[outIndex] = BASE64_TABLE[(byte) idx0];

// 第二个字符
int idx1_part1;
if ((b4 & 0xFFFFFF80) == 0) {
                    idx1_part1 = b4 >> 4;
                } else {
                    idx1_part1 = (b4 >> 4) ^ 0xF0;
                }
intidx1= idx1_part1 | ((b3 & 3) << 4);
                output[outIndex + 1] = BASE64_TABLE[(byte) idx1];

// 第三个字符
intidx2= (b4 & 15) << 2;
                output[outIndex + 2] = BASE64_TABLE[(byte) idx2];
// 第四个字符补'='
                output[outIndex + 3] = '=';
returnnewString(output);
            }
default: {
returnnewString(output);
            }
        }

// 处理8位剩余的情况(1字节)
byteb5= (byte) (data[inIndex] & 3);
intv8= data[inIndex] & 0xFFFFFF80;
intv9= data[inIndex] >> 2;
if (v8 != 0) {
            v9 ^= 0xC0;
        }

        output[outIndex] = BASE64_TABLE[(byte) v9];
        output[outIndex + 1] = BASE64_TABLE[b5 << 4];
// 后两个字符补'='
        output[outIndex + 2] = '=';
        output[outIndex + 3] = '=';
returnnewString(output);
    }
}

解密算法

寻找响应解密逻辑

原本我是打算直接分析 ya00.N 方法的,但是被混淆太严重了,索性我们使用算法助手直接把Signature类下的所有方法进行hook (其实我分析找到了这个程序的日志记录方法kb0.m方法),hook Signature 类下的所有方法发现每次请求后 gc 方法都会被调用,合理且大胆的猜测它就是,去 so 里看一眼也没事不是吗,查看之后发现它就是进行解密的方法。

 复制代码 隐藏代码
__int64 __fastcall Java_com_lvcha_main_util_Signature_gc(JNIEnv *env)
{
void *v2; // x20
void *v3; // x23
  __int64 v4; // x21

if ( byte_EAA58 != 1 )
return0;
  v2 = (void *)_JNIEnv::CallStaticObjectMethod(env, qword_EAA60, qword_EAA68);
  v3 = (void *)_JNIEnv::CallStaticObjectMethod(env, qword_EAA60, qword_EAA88);
  v4 = _JNIEnv::CallStaticObjectMethod(env, qword_EAA60, qword_EAA90);
  (*env)->DeleteLocalRef(env, v3);
if ( v2 )
    (*env)->DeleteLocalRef(env, v2);
return v4;
}

通过分析 Java_com_lvcha_main_util_Signature_gc 调用的Java层方法确实是在解密,只不过它返回的是字节数组,hook时我们看不到它加密后的字符形式

在 gc 的调用者处(只要w.w方法是真的有个是假的)发现 Signature.gc 的返回值直接被转成字符串了 然后又通过 kb0.m 打印转化后的结果,我们再次去算法助手查看会发现确实是响应的结果,至此我们完成了请求的加解密逻辑的分析

 复制代码 隐藏代码
Strings3=newString(Signature.gc(s2));
kb0.m(this.y.a, false, hl1.a("f1unhbY=\n", "HTTD/IydSGA=\n") + s3);  // a方法返回值:  body:
s2 = s3;

下面是我让AI复原的解密算法逻辑(我更熟悉Java就用Java复原了)

 复制代码 隐藏代码
package com.abjlvcha.main.util;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/*
  SecurityGCProvider 类
  实现了基于 AES-GCM 加密的签名功能
  包含自定义 Base64 解码和 AES-GCM 加密/解密方法
 */

@SuppressWarnings("all")
publicclassSecurityGCProvider {
/**
     * Base64 字符映射表
     * 用于自定义 Base64 解码
     */

privatestaticfinalbyte[] BASE64_TABLE;

/**
     * 线程本地存储的 IV (初始化向量) 数组
     * 长度为 12 字节,用于 AES-GCM 加密
     */

privatestaticfinal ThreadLocal<byte[]> IV_HOLDER;

/**
     * 线程本地存储的 SecureRandom 实例
     * 用于生成随机 IV
     */

privatestaticfinal ThreadLocal<SecureRandom> RANDOM_HOLDER;

/**
     * 线程本地存储的 Cipher 实例
     * 用于 AES-GCM 加密/解密操作
     */

privatestaticfinal ThreadLocal<Cipher> CIPHER_HOLDER;

static {
// 初始化 Base64 字符映射表
        BASE64_TABLE = newbyte[0x80];
        Arrays.fill(BASE64_TABLE, (byte) -1);

// 大写字母 A-Z 映射到 0-25
for (inti=90; i >= 65; --i) {
            BASE64_TABLE[i] = (byte) (i - 65);
        }

// 小写字母 a-z 映射到 26-51
for (inti=0x7A; i >= 97; --i) {
            BASE64_TABLE[i] = (byte) (i - 71);
        }

// 数字 0-9 映射到 52-61
for (inti=57; i >= 0x30; --i) {
            BASE64_TABLE[i] = (byte) (i + 4);
        }

// '+' 映射到 62
        BASE64_TABLE[43] = 62;
// '/' 映射到 63
        BASE64_TABLE[0x2F] = 0x3F;

// 初始化线程本地存储
        IV_HOLDER = newThreadLocal<byte[]>() {
@Override
protectedbyte[] initialValue() {
returnnewbyte[12]; // 12 字节 IV
            }
        };

        RANDOM_HOLDER = newThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
returnnewSecureRandom();
            }
        };

        CIPHER_HOLDER = newThreadLocal<Cipher>() {
@Override
protected Cipher initialValue() {
try {
return Cipher.getInstance("AES/GCM/NoPadding");
                } catch (Exception e) {
thrownewRuntimeException(e);
                }
            }
        };
    }

/**
     * 自定义 Base64 解码方法
     *
     * @param s Base64 编码的字符串
     * @return 解码后的字节数组
     */

publicstaticbyte[] base64Decode(String s) {
if (s == null) {
returnnull;
        }

char[] arr_c = s.toCharArray();
intv= filterWhitespace(arr_c); // 去除空白字符
if (v % 4 != 0) {
returnnull; // Base64 长度必须是 4 的倍数
        }

intv1= v / 4;
if (v1 == 0) {
returnnewbyte[0]; // 空字符串
        }

byte[] arr_b1 = newbyte[v1 * 3]; // 每 4 个 Base64 字符对应 3 个字节
intv2=0;
intv3=0;
intv4=0;

// 处理完整的 4 字符组
while (v2 < v1 - 1) {
intv5= v3 + 1;
charc= arr_c[v3];
if (isValidBase64Char(c)) {
intv6= v3 + 2;
charc1= arr_c[v5];
if (!isValidBase64Char(c1)) {
returnnull;
                }

intv7= v3 + 3;
charc2= arr_c[v6];
if (!isValidBase64Char(c2)) {
returnnull;
                }

                v3 += 4;
charc3= arr_c[v7];
if (!isValidBase64Char(c3)) {
returnnull;
                }

// 解码 4 个 Base64 字符为 3 个字节
byteb= BASE64_TABLE[c];
byteb1= BASE64_TABLE[c1];
byteb2= BASE64_TABLE[c2];
byteb3= BASE64_TABLE[c3];
                arr_b1[v4] = (byte) (b << 2 | b1 >> 4);
intv8= v4 + 2;
                arr_b1[v4 + 1] = (byte) ((b1 & 15) << 4 | b2 >> 2 & 15);
                v4 += 3;
                arr_b1[v8] = (byte) (b2 << 6 | b3);
                ++v2;
continue;
            }

returnnull;
        }

// 处理最后一组 4 字符(可能包含填充)
intv9= v3 + 1;
charc4= arr_c[v3];
byte[] arr_b = null;
if (isValidBase64Char(c4)) {
intv10= v3 + 2;
charc5= arr_c[v9];
if (isValidBase64Char(c5)) {
byte[] arr_b2 = BASE64_TABLE;
byteb4= arr_b2[c4];
byteb5= arr_b2[c5];
charc6= arr_c[v10];
charc7= arr_c[v3 + 3];

// 无填充情况
if ((isValidBase64Char(c6)) && (isValidBase64Char(c7))) {
byteb6= arr_b2[c6];
byteb7= arr_b2[c7];
                    arr_b1[v4] = (byte) (b4 << 2 | b5 >> 4);
                    arr_b1[v4 + 1] = (byte) ((b5 & 15) << 4 | b6 >> 2 & 15);
                    arr_b1[v4 + 2] = (byte) (b7 | b6 << 6);
return arr_b1;
                }

// 两个填充字符情况(解码为 1 个字节)
if ((isPadding(c6)) && (isPadding(c7))) {
if ((b5 & 15) != 0) {
returnnull;
                    }

intv11= v2 * 3;
byte[] arr_b3 = newbyte[v11 + 1];
                    System.arraycopy(arr_b1, 0, arr_b3, 0, v11);
                    arr_b3[v4] = (byte) (b4 << 2 | b5 >> 4);
return arr_b3;
                }

// 一个填充字符情况(解码为 2 个字节)
if (!isPadding(c6) && (isPadding(c7))) {
byteb8= arr_b2[c6];
if ((b8 & 3) != 0) {
returnnull;
                    }

intv12= v2 * 3;
                    arr_b = newbyte[v12 + 2];
                    System.arraycopy(arr_b1, 0, arr_b, 0, v12);
                    arr_b[v4] = (byte) (b4 << 2 | b5 >> 4);
                    arr_b[v4 + 1] = (byte) (b8 >> 2 & 15 | (b5 & 15) << 4);
                }
            }
        }

return arr_b;
    }

/**
     * 检查字符是否为有效的 Base64 字符
     *
     * @param c 待检查的字符
     * @return 是否为有效的 Base64 字符
     */

privatestaticbooleanisValidBase64Char(char c) {
return c < 0x80 && BASE64_TABLE[c] != -1;
    }

/**
     * 检查字符是否为 Base64 填充字符 '='
     *
     * @param c 待检查的字符
     * @return 是否为填充字符
     */

privatestaticbooleanisPadding(char c) {
return c == 61; // '=' 的 ASCII 码
    }

/**
     * 检查字符是否为空白字符
     *
     * @param c 待检查的字符
     * @return 是否为空白字符
     */

privatestaticbooleanisWhitespace(char c) {
return c == 0x20 || c == 13 || c == 10 || c == 9; // 空格、回车、换行、制表符
    }

/**
     * 过滤字符串中的空白字符
     *
     * @param arr_c 字符数组
     * @return 过滤后的字符长度
     */

privatestaticintfilterWhitespace(char[] arr_c) {
intv=0;
if (arr_c == null) {
return0;
        }

intv1=0;
while (v < arr_c.length) {
if (!isWhitespace(arr_c[v])) {
                arr_c[v1] = arr_c[v];
                ++v1;
            }

            ++v;
        }

return v1;
    }

/**
     * 生成随机 IV (初始化向量)
     *
     * @return 12 字节的随机 IV
     */

privatestaticbyte[] generateRandomIV() {
byte[] iv = IV_HOLDER.get();
SecureRandomrandom= RANDOM_HOLDER.get();
        random.nextBytes(iv);
        random.nextInt(); // 增加随机性
return iv;
    }

/**
     * AES-GCM 解密方法
     *
     * @param encryptedData 加密数据(包含 IV)
     * @param key           解密密钥
     * @return 解密后的数据
     */

publicstaticbyte[] aesGcmDecrypt(byte[] encryptedData, byte[] key) {
try {
byte[] iv = IV_HOLDER.get();
// 从加密数据中提取前 12 字节作为 IV
            System.arraycopy(encryptedData, 0, iv, 0, iv.length);

Ciphercipher= CIPHER_HOLDER.get();
SecretKeySpecsecretKeySpec=newSecretKeySpec(key, "AES");
// GCM 模式,认证标签长度为 128 位
GCMParameterSpecgcmParameterSpec=newGCMParameterSpec(0x80, iv);

synchronized (cipher) { // 线程安全处理
                cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
// 解密 IV 之后的数据
return cipher.doFinal(encryptedData, iv.length, encryptedData.length - iv.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
returnnull;
        }
    }

/**
     * AES-GCM 加密方法
     *
     * @param data 待加密数据
     * @param key  加密密钥
     * @return 加密后的数据(包含 IV)
     */

publicstaticbyte[] aesGcmEncrypt(byte[] data, byte[] key) {
try {
byte[] iv = generateRandomIV();
byte[] ivCopy = Arrays.copyOf(iv, iv.length);

Ciphercipher= CIPHER_HOLDER.get();
SecretKeySpecsecretKeySpec=newSecretKeySpec(key, "AES");
GCMParameterSpecgcmParameterSpec=newGCMParameterSpec(0x80, iv);

synchronized (cipher) { // 线程安全处理
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
byte[] encrypted = cipher.doFinal(data);

// 拼接 IV 和密文
byte[] result = newbyte[ivCopy.length + encrypted.length];
                System.arraycopy(ivCopy, 0, result, 0, ivCopy.length);
                System.arraycopy(encrypted, 0, result, ivCopy.length, encrypted.length);
return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
returnnull;
        }
    }

/**
     * gc 签名方法
     *
     * @param input Base64 编码的加密数据
     * @param uid   密钥(字符串形式)
     * @return 解密后的字符串
     */

publicstatic String gc(String input, String uid) {
try {
// 1. Base64 解码输入数据
byte[] inputBytes = base64Decode(input);
if (inputBytes == null) {
returnnull;
            }

// 2. 将 uid 转换为 UTF-8 字节数组作为密钥
byte[] uidBytes = uid.getBytes(StandardCharsets.UTF_8);

// 3. 使用 AES-GCM 解密数据
byte[] decrypted = aesGcmDecrypt(inputBytes, uidBytes);
if (decrypted == null) {
returnnull;
            }

// 4. 将解密后的数据转换为字符串并返回
returnnewString(decrypted);
        } catch (Exception e) {
            e.printStackTrace();
returnnull;
        }
    }
}

解密一个响应看一下:

 复制代码 隐藏代码
HTTP/1.1 200
Server: nginx/1.24.0 (Ubuntu)
Date: Sun, 05 Apr 2026 09:36:11 GMT
Content-Type: text/plain;charset=UTF-8
Content-Length: 148
Connection: keep-alive

dNdN/BzOb8KwdjEeit7ZuorlcazH95UG/8IvT1+hH89hnpI49Sa7GLNkKus9BiXuHR8fyWmzHTBc2EGMbXTkMkFpXjwkbLxHsbN9pWpmBayZjCvX/wtOFswaohzhe6SoRK2ZaJMT2t9SCux3Pw==

解密结果

 复制代码 隐藏代码
{
"code":1,
"body":{
"lottery_count":0,
"lottery_time":47069,
"lottery_number":11283
}
}

谢谢您的观看,文章有很多不详细的地方,望海涵。

· 今 日 推 荐 ·

相关学习资料