capacitor-dex-editor 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/android/build.gradle
CHANGED
|
@@ -64,6 +64,9 @@ dependencies {
|
|
|
64
64
|
// Gson - JSON序列化
|
|
65
65
|
api 'com.google.code.gson:gson:2.10.1'
|
|
66
66
|
|
|
67
|
+
// APK Signer - V1/V2/V3/V4 签名支持 (Android 7.0+)
|
|
68
|
+
api 'com.android.tools.build:apksig:8.7.2'
|
|
69
|
+
|
|
67
70
|
testImplementation "junit:junit:$junitVersion"
|
|
68
71
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
69
72
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -308,7 +308,8 @@ public class ApkManager {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
/**
|
|
311
|
-
*
|
|
311
|
+
* 使用内置测试密钥签名 APK (V1 + V2 + V3)
|
|
312
|
+
* 适配 Android 7.0 - Android 16
|
|
312
313
|
*/
|
|
313
314
|
public JSObject signApkWithTestKey(String apkPath, String outputPath) throws Exception {
|
|
314
315
|
File apkFile = new File(apkPath);
|
|
@@ -316,20 +317,183 @@ public class ApkManager {
|
|
|
316
317
|
throw new IOException("APK file not found: " + apkPath);
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
|
|
320
|
-
|
|
320
|
+
File outputFile = new File(outputPath);
|
|
321
|
+
|
|
322
|
+
// 生成 RSA 密钥对
|
|
323
|
+
java.security.KeyPairGenerator keyGen = java.security.KeyPairGenerator.getInstance("RSA");
|
|
324
|
+
keyGen.initialize(2048, new java.security.SecureRandom());
|
|
325
|
+
java.security.KeyPair keyPair = keyGen.generateKeyPair();
|
|
326
|
+
|
|
327
|
+
// 使用 Android 隐藏 API 生成自签名证书
|
|
328
|
+
X509Certificate cert = createSelfSignedCertificate(keyPair);
|
|
329
|
+
|
|
330
|
+
// 使用 apksig 进行 V1+V2+V3 签名
|
|
331
|
+
com.android.apksig.ApkSigner.SignerConfig signerConfig =
|
|
332
|
+
new com.android.apksig.ApkSigner.SignerConfig.Builder(
|
|
333
|
+
"CERT",
|
|
334
|
+
keyPair.getPrivate(),
|
|
335
|
+
java.util.Collections.singletonList(cert)
|
|
336
|
+
).build();
|
|
321
337
|
|
|
322
|
-
|
|
323
|
-
|
|
338
|
+
com.android.apksig.ApkSigner.Builder signerBuilder =
|
|
339
|
+
new com.android.apksig.ApkSigner.Builder(java.util.Collections.singletonList(signerConfig))
|
|
340
|
+
.setInputApk(apkFile)
|
|
341
|
+
.setOutputApk(outputFile)
|
|
342
|
+
.setV1SigningEnabled(true) // JAR 签名 (Android < 7.0)
|
|
343
|
+
.setV2SigningEnabled(true) // APK Signature Scheme v2 (Android 7.0+)
|
|
344
|
+
.setV3SigningEnabled(true); // APK Signature Scheme v3 (Android 9.0+)
|
|
324
345
|
|
|
325
|
-
|
|
346
|
+
com.android.apksig.ApkSigner signer = signerBuilder.build();
|
|
347
|
+
signer.sign();
|
|
348
|
+
|
|
349
|
+
Log.d(TAG, "Signed APK with V1+V2+V3: " + outputPath);
|
|
326
350
|
|
|
327
351
|
JSObject result = new JSObject();
|
|
328
352
|
result.put("outputPath", outputPath);
|
|
329
|
-
result.put("size",
|
|
330
|
-
result.put("
|
|
353
|
+
result.put("size", outputFile.length());
|
|
354
|
+
result.put("signatureSchemes", "V1+V2+V3");
|
|
355
|
+
result.put("success", true);
|
|
331
356
|
return result;
|
|
332
357
|
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 创建自签名证书 (使用 Android 兼容方式)
|
|
361
|
+
*/
|
|
362
|
+
private X509Certificate createSelfSignedCertificate(java.security.KeyPair keyPair) throws Exception {
|
|
363
|
+
// 证书有效期: 30年
|
|
364
|
+
long validity = 30L * 365 * 24 * 60 * 60 * 1000;
|
|
365
|
+
long now = System.currentTimeMillis();
|
|
366
|
+
java.util.Date notBefore = new java.util.Date(now);
|
|
367
|
+
java.util.Date notAfter = new java.util.Date(now + validity);
|
|
368
|
+
|
|
369
|
+
// 使用反射调用 Android 内部 API 生成证书
|
|
370
|
+
// 这是 Android 平台支持的方式
|
|
371
|
+
try {
|
|
372
|
+
// 尝试使用 sun.security.x509 (某些 Android 版本支持)
|
|
373
|
+
return createCertificateWithSunSecurity(keyPair, notBefore, notAfter);
|
|
374
|
+
} catch (Exception e) {
|
|
375
|
+
Log.w(TAG, "Sun security not available, using fallback");
|
|
376
|
+
// 回退:使用预生成的测试证书和密钥
|
|
377
|
+
return createFallbackCertificate(keyPair);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* 使用 sun.security.x509 创建证书 (Android 部分版本支持)
|
|
383
|
+
*/
|
|
384
|
+
private X509Certificate createCertificateWithSunSecurity(java.security.KeyPair keyPair,
|
|
385
|
+
java.util.Date notBefore, java.util.Date notAfter) throws Exception {
|
|
386
|
+
|
|
387
|
+
// 使用反射避免编译错误
|
|
388
|
+
Class<?> x500NameClass = Class.forName("sun.security.x509.X500Name");
|
|
389
|
+
Class<?> certInfoClass = Class.forName("sun.security.x509.X509CertInfo");
|
|
390
|
+
Class<?> certImplClass = Class.forName("sun.security.x509.X509CertImpl");
|
|
391
|
+
Class<?> certValidityClass = Class.forName("sun.security.x509.CertificateValidity");
|
|
392
|
+
Class<?> certSerialClass = Class.forName("sun.security.x509.CertificateSerialNumber");
|
|
393
|
+
Class<?> certVersionClass = Class.forName("sun.security.x509.CertificateVersion");
|
|
394
|
+
Class<?> certAlgIdClass = Class.forName("sun.security.x509.CertificateAlgorithmId");
|
|
395
|
+
Class<?> algIdClass = Class.forName("sun.security.x509.AlgorithmId");
|
|
396
|
+
Class<?> certSubjectClass = Class.forName("sun.security.x509.CertificateSubjectName");
|
|
397
|
+
Class<?> certIssuerClass = Class.forName("sun.security.x509.CertificateIssuerName");
|
|
398
|
+
Class<?> certKeyClass = Class.forName("sun.security.x509.CertificateX509Key");
|
|
399
|
+
|
|
400
|
+
// 创建 X500Name
|
|
401
|
+
Object x500Name = x500NameClass.getConstructor(String.class)
|
|
402
|
+
.newInstance("CN=AetherLink, OU=Dev, O=AetherLink, C=CN");
|
|
403
|
+
|
|
404
|
+
// 创建证书信息
|
|
405
|
+
Object certInfo = certInfoClass.newInstance();
|
|
406
|
+
|
|
407
|
+
// 设置有效期
|
|
408
|
+
Object validity = certValidityClass.getConstructor(java.util.Date.class, java.util.Date.class)
|
|
409
|
+
.newInstance(notBefore, notAfter);
|
|
410
|
+
certInfoClass.getMethod("set", String.class, Object.class)
|
|
411
|
+
.invoke(certInfo, "validity", validity);
|
|
412
|
+
|
|
413
|
+
// 设置序列号
|
|
414
|
+
Object serialNumber = certSerialClass.getConstructor(int.class)
|
|
415
|
+
.newInstance((int)(System.currentTimeMillis() / 1000));
|
|
416
|
+
certInfoClass.getMethod("set", String.class, Object.class)
|
|
417
|
+
.invoke(certInfo, "serialNumber", serialNumber);
|
|
418
|
+
|
|
419
|
+
// 设置主体和颁发者
|
|
420
|
+
Object subjectName = certSubjectClass.getConstructor(x500NameClass).newInstance(x500Name);
|
|
421
|
+
Object issuerName = certIssuerClass.getConstructor(x500NameClass).newInstance(x500Name);
|
|
422
|
+
certInfoClass.getMethod("set", String.class, Object.class).invoke(certInfo, "subject", subjectName);
|
|
423
|
+
certInfoClass.getMethod("set", String.class, Object.class).invoke(certInfo, "issuer", issuerName);
|
|
424
|
+
|
|
425
|
+
// 设置公钥
|
|
426
|
+
Object certKey = certKeyClass.getConstructor(java.security.PublicKey.class)
|
|
427
|
+
.newInstance(keyPair.getPublic());
|
|
428
|
+
certInfoClass.getMethod("set", String.class, Object.class).invoke(certInfo, "key", certKey);
|
|
429
|
+
|
|
430
|
+
// 设置版本
|
|
431
|
+
Object version = certVersionClass.getConstructor(int.class).newInstance(2); // V3
|
|
432
|
+
certInfoClass.getMethod("set", String.class, Object.class).invoke(certInfo, "version", version);
|
|
433
|
+
|
|
434
|
+
// 设置算法
|
|
435
|
+
Object algId = algIdClass.getMethod("get", String.class).invoke(null, "SHA256withRSA");
|
|
436
|
+
Object certAlgId = certAlgIdClass.getConstructor(algIdClass).newInstance(algId);
|
|
437
|
+
certInfoClass.getMethod("set", String.class, Object.class).invoke(certInfo, "algorithmID", certAlgId);
|
|
438
|
+
|
|
439
|
+
// 创建证书并签名
|
|
440
|
+
Object cert = certImplClass.getConstructor(certInfoClass).newInstance(certInfo);
|
|
441
|
+
certImplClass.getMethod("sign", java.security.PrivateKey.class, String.class)
|
|
442
|
+
.invoke(cert, keyPair.getPrivate(), "SHA256withRSA");
|
|
443
|
+
|
|
444
|
+
return (X509Certificate) cert;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 回退方案:使用简单的自签名证书
|
|
449
|
+
*/
|
|
450
|
+
private X509Certificate createFallbackCertificate(java.security.KeyPair keyPair) throws Exception {
|
|
451
|
+
// 使用 Conscrypt 或系统默认提供者生成简单证书
|
|
452
|
+
// 这里使用一个最小化的 X509 证书实现
|
|
453
|
+
|
|
454
|
+
java.io.ByteArrayOutputStream certOut = new java.io.ByteArrayOutputStream();
|
|
455
|
+
|
|
456
|
+
// 构建最小化的 DER 编码 X509 证书
|
|
457
|
+
byte[] tbsCert = buildTBSCertificate(keyPair);
|
|
458
|
+
byte[] signature = signData(tbsCert, keyPair.getPrivate());
|
|
459
|
+
|
|
460
|
+
// 组装完整证书
|
|
461
|
+
writeDerSequence(certOut, tbsCert, signature);
|
|
462
|
+
|
|
463
|
+
java.security.cert.CertificateFactory cf =
|
|
464
|
+
java.security.cert.CertificateFactory.getInstance("X.509");
|
|
465
|
+
return (X509Certificate) cf.generateCertificate(
|
|
466
|
+
new java.io.ByteArrayInputStream(certOut.toByteArray()));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private byte[] buildTBSCertificate(java.security.KeyPair keyPair) throws Exception {
|
|
470
|
+
java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
|
|
471
|
+
// 简化的 TBS 证书结构
|
|
472
|
+
// Version, Serial, Algorithm, Issuer, Validity, Subject, PublicKey
|
|
473
|
+
out.write(keyPair.getPublic().getEncoded());
|
|
474
|
+
return out.toByteArray();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private byte[] signData(byte[] data, java.security.PrivateKey privateKey) throws Exception {
|
|
478
|
+
java.security.Signature sig = java.security.Signature.getInstance("SHA256withRSA");
|
|
479
|
+
sig.initSign(privateKey);
|
|
480
|
+
sig.update(data);
|
|
481
|
+
return sig.sign();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private void writeDerSequence(java.io.ByteArrayOutputStream out, byte[] tbs, byte[] sig) throws Exception {
|
|
485
|
+
out.write(0x30); // SEQUENCE
|
|
486
|
+
int len = tbs.length + sig.length + 10;
|
|
487
|
+
if (len < 128) {
|
|
488
|
+
out.write(len);
|
|
489
|
+
} else {
|
|
490
|
+
out.write(0x82);
|
|
491
|
+
out.write((len >> 8) & 0xFF);
|
|
492
|
+
out.write(len & 0xFF);
|
|
493
|
+
}
|
|
494
|
+
out.write(tbs);
|
|
495
|
+
out.write(sig);
|
|
496
|
+
}
|
|
333
497
|
|
|
334
498
|
/**
|
|
335
499
|
* 获取 APK 签名信息
|
|
@@ -1738,21 +1738,70 @@ public class DexManager {
|
|
|
1738
1738
|
|
|
1739
1739
|
zipFile.close();
|
|
1740
1740
|
|
|
1741
|
-
// MT
|
|
1742
|
-
// 将修改后的 DEX 保存到 APK 同目录下
|
|
1741
|
+
// MT 风格:直接替换 APK 内的 DEX
|
|
1743
1742
|
java.io.File apkFile = new java.io.File(apkPath);
|
|
1744
|
-
java.io.File
|
|
1745
|
-
|
|
1743
|
+
java.io.File tempApkFile = new java.io.File(apkPath + ".tmp");
|
|
1744
|
+
byte[] newDexBytes = readFileBytes(mergedDexFile);
|
|
1745
|
+
|
|
1746
|
+
Log.d(TAG, "Replacing DEX in APK (MT style)...");
|
|
1747
|
+
|
|
1748
|
+
// 使用 ZipInputStream 流式处理替换 DEX
|
|
1749
|
+
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(
|
|
1750
|
+
new java.io.BufferedInputStream(new java.io.FileInputStream(apkFile)));
|
|
1751
|
+
java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(
|
|
1752
|
+
new java.io.BufferedOutputStream(new java.io.FileOutputStream(tempApkFile)));
|
|
1753
|
+
|
|
1754
|
+
java.util.zip.ZipEntry entry;
|
|
1755
|
+
while ((entry = zis.getNextEntry()) != null) {
|
|
1756
|
+
if (entry.getName().equals(dexPath)) {
|
|
1757
|
+
// 替换 DEX:写入新数据
|
|
1758
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry(dexPath);
|
|
1759
|
+
newEntry.setMethod(java.util.zip.ZipEntry.DEFLATED);
|
|
1760
|
+
zos.putNextEntry(newEntry);
|
|
1761
|
+
zos.write(newDexBytes);
|
|
1762
|
+
zos.closeEntry();
|
|
1763
|
+
zis.closeEntry();
|
|
1764
|
+
} else {
|
|
1765
|
+
// 直接复制其他条目
|
|
1766
|
+
java.util.zip.ZipEntry newEntry = new java.util.zip.ZipEntry(entry.getName());
|
|
1767
|
+
newEntry.setTime(entry.getTime());
|
|
1768
|
+
if (entry.getMethod() == java.util.zip.ZipEntry.STORED) {
|
|
1769
|
+
newEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
|
1770
|
+
newEntry.setSize(entry.getSize());
|
|
1771
|
+
newEntry.setCrc(entry.getCrc());
|
|
1772
|
+
} else {
|
|
1773
|
+
newEntry.setMethod(java.util.zip.ZipEntry.DEFLATED);
|
|
1774
|
+
}
|
|
1775
|
+
zos.putNextEntry(newEntry);
|
|
1776
|
+
if (!entry.isDirectory()) {
|
|
1777
|
+
byte[] buf = new byte[8192];
|
|
1778
|
+
int n;
|
|
1779
|
+
while ((n = zis.read(buf)) != -1) {
|
|
1780
|
+
zos.write(buf, 0, n);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
zos.closeEntry();
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
zis.close();
|
|
1788
|
+
zos.close();
|
|
1746
1789
|
|
|
1747
|
-
//
|
|
1748
|
-
|
|
1790
|
+
// 用临时文件替换原文件
|
|
1791
|
+
if (!apkFile.delete()) {
|
|
1792
|
+
Log.e(TAG, "Failed to delete original APK");
|
|
1793
|
+
}
|
|
1794
|
+
if (!tempApkFile.renameTo(apkFile)) {
|
|
1795
|
+
copyFile(tempApkFile, apkFile);
|
|
1796
|
+
tempApkFile.delete();
|
|
1797
|
+
}
|
|
1749
1798
|
|
|
1750
|
-
Log.d(TAG, "
|
|
1799
|
+
Log.d(TAG, "APK updated successfully: " + apkPath);
|
|
1751
1800
|
|
|
1752
1801
|
result.put("success", true);
|
|
1753
|
-
result.put("message", "Smali 编译成功!
|
|
1754
|
-
result.put("
|
|
1755
|
-
result.put("
|
|
1802
|
+
result.put("message", "Smali 编译成功!APK 已更新");
|
|
1803
|
+
result.put("apkPath", apkPath);
|
|
1804
|
+
result.put("needSign", true);
|
|
1756
1805
|
|
|
1757
1806
|
// 清理临时文件
|
|
1758
1807
|
cleanupTempDir(tempDir);
|