Android APK 簽名比對
發佈過Android應用的朋友們應該都知道,Android APK的發佈是須要簽名的。簽名機制在Android應用和框架中有着十分重要的做用。html
例如,Android系統禁止更新安裝簽名不一致的APK;若是應用須要使用system權限,必須保證APK簽名與Framework簽名一致,等等。在《APK Crack》一文中,咱們瞭解到,要破解一個APK,必然須要從新對APK進行簽名。而這個簽名,通常狀況沒法再與APK原先的簽名保持一致。(除非APK原做者的私鑰泄漏,那已是另外一個層次的軟件安全問題了。)java
簡單地說,簽名機制標明瞭APK的發行機構。所以,站在軟件安全的角度,咱們就能夠經過比對APK的簽名狀況,判斷此APK是否由「官方」發行,而不是被破解篡改太重新簽名打包的「盜版軟件」。android
Android簽名機制
爲了說明APK簽名比對對軟件安全的有效性,咱們有必要了解一下Android APK的簽名機制。爲了更易於你們理解,咱們從Auto-Sign工具的一條批處理命令提及。算法
在《APK Crack》一文中,咱們瞭解到,要簽名一個沒有簽名過的APK,可使用一個叫做Auto-sign的工具。Auto-sign工具實際運行的是一個叫作Sign.bat的批處理命令。用文本編輯器打開這個批處理文件,咱們能夠發現,實現簽名功能的命令主要是這一行命令:安全
java
-
jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
這條命令的意義是:經過signapk.jar這個可執行jar包,以「
testkey.x509.pem
」這個公鑰文件和「
testkey.pk8
」這個私鑰文件對「
update.apk
」進行簽名,簽名後的文件保存爲「
update_signed.apk
」。
對於此處所使用的私鑰和公鑰的生成方式,這裏就不作進一步介紹了。這方面的資料你們能夠找到不少。咱們這裏要講的是signapk.jar到底作了什麼。網絡
signapk.jar是Android源碼包中的一個簽名工具。因爲Android是個開源項目,因此,很高興地,咱們能夠直接找到signapk.jar的源碼!路徑爲/build/tools/signapk/SignApk.java。框架
對比一個沒有簽名的APK和一個簽名好的APK,咱們會發現,簽名好的APK包中多了一個叫作META-INF的文件夾。裏面有三個文件,分別名爲MANIFEST.MF、CERT.SF和CERT.RSA。signapk.jar就是生成了這幾個文件(其餘文件沒有任何改變。所以咱們能夠很容易去掉原有簽名信息)。編輯器
經過閱讀signapk源碼,咱們能夠理清簽名APK包的整個過程。
函數
一、 生成MANIFEST.MF文件:
程序遍歷update.apk包中的全部文件(entry),對非文件夾非簽名文件的文件,逐個生成SHA1的數字簽名信息,再用Base64進行編碼。具體代碼見這個方法:
private
static
Manifest addDigestsToManifest(JarFile jar)
關鍵代碼以下:
1
for
(JarEntry entry: byName.values()) {
2
String name
=
entry.getName();
3
if
(
!
entry.isDirectory()
&&
!
name.equals(JarFile.MANIFEST_NAME)
&&
4
!
name.equals(CERT_SF_NAME)
&&
!
name.equals(CERT_RSA_NAME)
&&
5
(stripPattern
==
null
||!
stripPattern.matcher(name).matches())) {
6
InputStream data
=
jar.getInputStream(entry);
7
while
((num
=
data.read(buffer))
>
0
) {
8
md.update(buffer,
0
, num);
9
}
10
Attributes attr
=
null
;
11
if
(input
!=
null
) attr
=
input.getAttributes(name);
12
attr
=
attr
!=
null
?
new
Attributes(attr) :
new
Attributes();
13
attr.putValue(
"
SHA1-Digest
"
, base64.encode(md.digest()));
14
output.getEntries().put(name, attr);
15
}
16
}
以後將生成的簽名寫入MANIFEST.MF文件。關鍵代碼以下:
1
Manifest manifest
=
addDigestsToManifest(inputJar);
2
je
=
new
JarEntry(JarFile.MANIFEST_NAME);
3
je.setTime(timestamp);
4
outputJar.putNextEntry(je);
5
manifest.write(outputJar);
這裏簡單介紹下SHA1數字簽名。簡單地說,它就是一種安全哈希算法,相似於MD5算法。它把任意長度的輸入,經過散列算法變成固定長度的輸出(這裏咱們稱做「摘要信息」)。你不能僅經過這個摘要信息復原原來的信息。另外,它保證不一樣信息的摘要信息彼此不一樣。所以,若是你改變了apk包中的文件,那麼在apk安裝校驗時,改變後的文件摘要信息與MANIFEST.MF的檢驗信息不一樣,因而程序就不能成功安裝。
二、 生成CERT.SF文件:
對前一步生成的Manifest,使用SHA1-RSA算法,用私鑰進行簽名。關鍵代碼以下:
1
Signature signature
=
Signature.getInstance(
"
SHA1withRSA
"
);
2
signature.initSign(privateKey);
3
je
=
new
JarEntry(CERT_SF_NAME);
4
je.setTime(timestamp);
5
outputJar.putNextEntry(je);
6
writeSignatureFile(manifest,
7
new
SignatureOutputStream(outputJar, signature));
RSA是一種非對稱加密算法。用私鑰經過RSA算法對摘要信息進行加密。在安裝時只能使用公鑰才能解密它。解密以後,將它與未加密的摘要信息進行對比,若是相符,則代表內容沒有被異常修改。
三、 生成CERT.RSA文件:
生成MANIFEST.MF沒有使用密鑰信息,生成CERT.SF文件使用了私鑰文件。那麼咱們能夠很容易猜想到,CERT.RSA文件的生成確定和公鑰相關。
CERT.RSA文件中保存了公鑰、所採用的加密算法等信息。核心代碼以下:
1
je
=
new
JarEntry(CERT_RSA_NAME);
2
je.setTime(timestamp);
3
outputJar.putNextEntry(je);
4
writeSignatureBlock(signature, publicKey, outputJar);
其中writeSignatureBlock的代碼以下:
1
private
static
void
writeSignatureBlock(
2
Signature signature, X509Certificate publicKey, OutputStream out)
3
throws
IOException, GeneralSecurityException {
4
SignerInfo signerInfo
=
new
SignerInfo(
5
new
X500Name(publicKey.getIssuerX500Principal().getName()),
6
publicKey.getSerialNumber(),
7
AlgorithmId.get(
"
SHA1
"
),
8
AlgorithmId.get(
"
RSA
"
),
9
signature.sign());
10
11
PKCS7 pkcs7
=
new
PKCS7(
12
new
AlgorithmId[] { AlgorithmId.get(
"
SHA1
"
) },
13
new
ContentInfo(ContentInfo.DATA_OID,
null
),
14
new
X509Certificate[] { publicKey },
15
new
SignerInfo[] { signerInfo });
16
17
pkcs7.encodeSignedData(out);
18
}
好了,分析完APK包的簽名流程,咱們能夠清楚地意識到:
一、 Android簽名機制實際上是對APK包完整性和發佈機構惟一性的一種校驗機制。
二、 Android簽名機制不能阻止APK包被修改,但修改後的再簽名沒法與原先的簽名保持一致。(擁有私鑰的狀況除外)。
三、 APK包加密的公鑰就打包在APK包內,且不一樣的私鑰對應不一樣的公鑰。換句話言之,不一樣的私鑰簽名的APK公鑰也必不相同。因此咱們能夠根據公鑰的對比,來判斷私鑰是否一致。
APK簽名比對的實現方式
好了,經過Android簽名機制的分析,咱們從理論上證實了經過APK公鑰的比對能判斷一個APK的發佈機構。而且這個發佈機構是很難假裝的,咱們暫時能夠認爲是不可假裝的。
有了理論基礎後,咱們就能夠開始實踐了。那麼如何獲取到APK文件的公鑰信息呢?由於Android系統安裝程序確定會獲取APK信息進行比對,因此咱們能夠經過Android源碼得到一些思路和幫助。
源碼中有一個隱藏的類用於APK包的解析。這個類叫PackageParser,路徑爲frameworks\base\core\java\android\content\pm\PackageParser.java。當咱們須要獲取APK包的相關信息時,能夠直接使用這個類,下面代碼就是一個例子函數:
1
private
PackageInfo parsePackage(String archiveFilePath,
int
flags){
2
3
PackageParser packageParser
=
new
PackageParser(archiveFilePath);
4
DisplayMetrics metrics
=
new
DisplayMetrics();
5
metrics.setToDefaults();
6
final
File sourceFile
=
new
File(archiveFilePath);
7
PackageParser.Package pkg
=
packageParser.parsePackage(
8
sourceFile, archiveFilePath, metrics,
0
);
9
if
(pkg
==
null
) {
10
return
null
;
11
}
12
13
packageParser.collectCertificates(pkg,
0
);
14
15
return
PackageParser.generatePackageInfo(pkg,
null
, flags,
0
,
0
);
16
}
其中參數archiveFilePath指定APK文件路徑;flags需設置PackageManager.GET_SIGNATURES位,以保證返回證書籤名信息。
具體如何經過PackageParser獲取簽名信息在此處不作詳述,具體代碼請參考PackageParser中的public boolean collectCertificates(Package pkg, int flags)和private Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer)方法。至於如何在Android應用開發中使用隱藏的類及方法,能夠參看個人這篇文章:《Android應用開發中如何使用隱藏API》。
緊接着,咱們就能夠經過packageInfo.signatures來訪問到APK的簽名信息。還須要說明的是 Android中Signature和Java中Certificate的對應關係。它們的關係以下面代碼所示:
1
pkg.mSignatures
=
new
Signature[certs.length];
2
for
(
int
i
=
0
; i
<
N; i
++
) {
3
pkg.mSignatures[i]
=
new
Signature(
4
certs[i].getEncoded());
5
}
也就是說signature = new Signature(certificate.getEncoded()); certificate證書中包含了公鑰和證書的其餘基本信息。公鑰不一樣,證書確定互不相同。咱們能夠經過certificate的getPublicKey方法獲取公鑰信息。因此比對簽名證書本質上就是比對公鑰信息。
OK,獲取到APK簽名證書以後,就剩下比對了。這個簡單,功能函數以下所示:
1
private
boolean
IsSignaturesSame(Signature[] s1, Signature[] s2) {
2
if
(s1
==
null
) {
3
return
false
;
4
}
5
if
(s2
==
null
) {
6
return
false
;
7
}
8
HashSet
<
Signature
>
set1
=
new
HashSet
<
Signature
>
();
9
for
(Signature sig : s1) {
10
set1.add(sig);
11
}
12
HashSet
<
Signature
>
set2
=
new
HashSet
<
Signature
>
();
13
for
(Signature sig : s2) {
14
set2.add(sig);
15
}
16
//
Make sure s2 contains all signatures in s1.
17
if
(set1.equals(set2)) {
18
return
true
;
19
}
20
return
false
;
21
}
APK簽名比對的應用場景
通過以上的論述,想必你們已經明白簽名比對的原理和個人實現方式了。那麼何時什麼狀況適合使用簽名對比來保障Android APK的軟件安全呢?
我的認爲主要有如下三種場景:
一、 程序自檢測。在程序運行時,自我進行簽名比對。比對樣本能夠存放在APK包內,也可存放於雲端。缺點是程序被破解時,自檢測功能一樣可能遭到破壞,使其失效。
二、 可信賴的第三方檢測。由可信賴的第三方程序負責APK的軟件安全問題。對比樣本由第三方收集,放在雲端。這種方式適用於殺毒安全軟件或者APP Market之類的軟件下載市場。缺點是須要聯網檢測,在無網絡狀況下沒法實現功能。(不可能把大量的簽名數據放在移動設備本地)。
三、 系統限定安裝。這就涉及到改Android系統了。限定僅能安裝某些證書的APK。軟件發佈商須要向系統發佈上申請證書。若是發現問題,能追蹤到是哪一個軟件發佈商的責任。適用於系統提供商或者終端產品生產商。缺點是過於封閉,不利於系統的開放性。
以上三種場景,雖然各有缺點,但缺點並非不能克服的。例如,咱們能夠考慮程序自檢測的功能用native method的方法實現等等。軟件安全是一個複雜的課題,每每須要多種技術聯合使用,才能更好的保障軟件不被惡意破壞。
參考資料
Android源碼
《Android中的簽名機制》