Android-sdk签名验证

为什么要有签名验证

假设“人民日报”接入了我们开发的sdk,“今日头条”没有接入我们的sdk,那么对于我们来说,当然不希望“今日头条”可以使用我们暴漏的接口,此时就有了“签名验证”。

如何实现

  1. 我们拿到“人民日报”的apk,通过 JarFile 拿到该apk的签名信息。
  2. 将该信息打入我们的aar包中
  3. 当“人民日报”访问我们暴漏的接口的时候要求传入Context,我们可以通过Context获取packageInfo中的signature。
  4. 比对aar中的签名信息跟获取到的packageinfo中的签名信息,如果匹配,才允许访问。

如何通过jarFile获取apk签名信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class getApkSign {

public static void main(String[] args) throws IOException {

String path = "F:/myapk/test.apk";//apk的路径

List<String> list = getSignaturesFromApk(path);
// for (String s : list) {
// System.out.println(s);
// }
System.out.println(list.get(0));
}

/**
* 从APK中读取签名
*
* @param file
* @return
* @throws IOException
*/
private static List<String> getSignaturesFromApk(String strFile)
throws IOException {
File file = new File(strFile);
List<String> signatures = new ArrayList<String>();
JarFile jarFile = new JarFile(file);
try {
JarEntry je = jarFile.getJarEntry("AndroidManifest.xml");
byte[] readBuffer = new byte[8192];
Certificate[] certs = loadCertificates(jarFile, je, readBuffer);
if (certs != null) {
for (Certificate c : certs) {
String sig = toCharsString(c.getEncoded());
signatures.add(sig);
}
}
} catch (Exception ex) {
}
return signatures;
}

/**
* 加载签名
*
* @param jarFile
* @param je
* @param readBuffer
* @return
*/
private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je,
byte[] readBuffer) {
try {
InputStream is = jarFile.getInputStream(je);
while (is.read(readBuffer, 0, readBuffer.length) != -1) {
}
is.close();
return je != null ? je.getCertificates() : null;
} catch (IOException e) {
}
return null;
}

/**
* 将签名转成转成可见字符串
*
* @param sigBytes
* @return
*/
private static String toCharsString(byte[] sigBytes) {
byte[] sig = sigBytes;
final int N = sig.length;
final int N2 = N * 2;
char[] text = new char[N2];
for (int j = 0; j < N; j++) {
byte v = sig[j];
int d = (v >> 4) & 0xf;
text[j * 2] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xf;
text[j * 2 + 1] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
return new String(text);
}

}

如何将签名信息打入aar

可以在build.gradle中新建task,该task有两个任务:

  1. 通过JarFile获取签名string(参考以上代码)
  2. 将该信息copy到jni层(假设coreLib.cpp中)出于安全考虑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
task('writeApkSignature') << {
// 1,获取签名信息
def signatures = []
JarFile jarFile = new JarFile(file(apkPath))
JarEntry je = jarFile.getJarEntry("AndroidManifest.xml")
byte[] readBuffer = new byte[8192]
InputStream is = jarFile.getInputStream(je)
while (is.read(readBuffer, 0, readBuffer.length) != -1) {
}
is.close()
Certificate[] certs = je.getCertificates()
if (certs != null) {
for (Certificate c : certs) {
byte[] sig = c.getEncoded()
println new String(sig)
int N = sig.length
int N2 = N * 2
char[] text = new char[N2]

char charA = 'a'
char char0 = '0'
for (int j = 0; j < N; j++) {
byte v = sig[j]
byte d = (v >> 4) & 0xf
text[j * 2] = (char) (d >= 10 ? (charA + d - 10) : (char0 + d));
d = v & 0xf
text[j * 2 + 1] = (char) (d >= 10 ? (charA + d - 10) : (char0 + d))
}
String sigStr = new String(text)
signatures.add(sigStr)
}
}
def signature = signatures.get(0)
println signature
// 2,拷贝
def cppFile = file("src/main/cpp/common-corelib.cpp")
def cppFileText = cppFile.getText()
StringBuilder sb = new StringBuilder(cppFileText)
int start = sb.indexOf("RELEASE_SIGN")
int end = sb.indexOf("\";", start) + 1
sb.replace(start, end, "RELEASE_SIGN = \""+ signature +"\"")


cppFile.write(sb.toString())
}

如何通过context获取签名信息(jni的方式)

  1. java方式

    1
    2
    3
    PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 64);
    Signature[] signatures = packageInfo.signatures;
    String s = signatures[0].toCharsString();
  2. jni方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    char *getSignature(JNIEnv *env, jclass clazz, jobject contextObject) {
    jclass native_class = env->GetObjectClass(contextObject);
    //getPackageManager方法id
    jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager",
    "()Landroid/content/pm/PackageManager;");
    //context.getPackageManager()->packageManager
    jobject pm_obj = env->CallObjectMethod(contextObject, pm_id);
    jclass pm_clazz = env->GetObjectClass(pm_obj);
    //获取pm.getpackageInfo的方法id
    jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo",
    "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jclass native_classs = env->GetObjectClass(contextObject);
    //context.getPackageName方法id
    jmethodID mId = env->GetMethodID(native_classs, "getPackageName", "()Ljava/lang/String;");
    //返回packageName
    jstring pkg_str = static_cast<jstring>(env->CallObjectMethod(contextObject, mId));
    //获取packageInfo
    jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);

    jclass pi_clazz = env->GetObjectClass(pi_obj);
    //获取packageInfo的成员变量signature的id
    jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures",
    "[Landroid/content/pm/Signature;");
    //获取packageInfo的signature
    jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
    jobjectArray signaturesArray = (jobjectArray) signatures_obj;
    //获取signatures[0]
    jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
    jclass signature_clazz = env->GetObjectClass(signature_obj);
    jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString",
    "()Ljava/lang/String;");
    //调用signatures[0]的toCharsString方法返回string串
    jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
    return (char *) env->GetStringUTFChars(str, 0);
    }