轉自:http://blog.csdn.net/yangkai6121/article/details/38682321
為什么需要給Android系統簽個名才能進行CTS認證呢?原來我們通過make -j4編譯出來的system.img使用的是test key,這種類型的key只適用於開發階段,而且這種秘鑰是公開的,誰都可以使用。當發布一款android產品,就需要另外給整個系統簽個名,防止被別 人盜用。這種系統就是release版本的Android系統。
一、 首先介紹關於簽名的作用
1、 APK簽名的作用:
a) 發送者的身份認證,由於開發商可能通過使用相同的Package Name來混淆替換已經安裝的程序,以此保證簽名不同的包不被替換。
b) 保證信息傳輸的完整性,簽名對於包中的每個文件進行處理,以此確保包中內容不被替換,防止交易中的抵賴發生,Market對軟件的要求。
2、 Recovery簽名作用:
Recovery進行簽名的主要作用是用來驗證數據的完整性。
3、要對Android系統進行簽名,需要生成四種類型的key文件。
a)releasekey (或testkey)
b)media
c)shared
d)platform
以上的四種,可以在源碼的/build/target/product/security里面看到對應的密鑰,其中shared.pk8代表私鑰,shared.x509.pem公鑰,一定是成對出現的。其中testkey是作為android編譯的時候默認的簽名key,如果系統中的apk的android.mk中沒有設置LOCAL_CERTIFICATE的值,就默認使用testkey。而如果設置成:LOCAL_CERTIFICATE := platform 就代表使用platform來簽名,這樣的話這個apk就擁有了和system相同的簽名,因為系統級別的簽名也是使用的platform來簽名,此時使用android:sharedUserId="android.uid.system"才有用!
二、 生成加密key文件
我們就拿releasekey為例簡單介紹下生成過程。Android使用SHA1-RSA算法進行簽名。可通過eclipse插件進行,可以通過keytool和jarsigner 用命令行執行。也可以在源碼下進行簽名。
1、以源碼下簽名方法來介紹生成密鑰對方法:
直接執行development/tools/make_key releasekey '/C=CN/ST=BeiJing/L=HaiDian View/O=JiuZhou/OU=PingTai/CN=lran/emailAddress=lran@jzby.com'生成releasekey.pk8和releasekey.x509.pem。其中releasekey 是指最后的加密方式為release方式,執行后顯示如下:
development/tools$ sh make_key releasekey '/C=CN/ST=JiangSu/L=NanJing/O=Company/OU=Department/CN=YourName/emailAddress=YourE-mailAddress'
Enter password for 'releasekey' (blank for none; password will be visible): mypassword <------- 設置你的密碼(一般直接回車,要不每次簽名apk,你還得手動輸入簽名密碼,后面講其他原因)
creating platform.pk8 with password [mypassword]
Generating RSA private key, 2048 bit long modulus
...............+++
........................................................+++
e is 3 (0x3)
key的名字很好理解,就是前面提到的4中類型的key,公司信息的參數比較多,它們的含義如下:
C ---> Country Name (2 letter code)
ST ---> State or Province Name (full name)
L ---> Locality Name (eg, city)
O ---> Organization Name (eg, company)
OU ---> Organizational Unit Name (eg, section)
CN ---> Common Name (eg, your name or your server’s hostname)
emailAddress ---> Contact email address
這樣就生成了一組releasekey,另外3種類型的key的生成方法也基本一樣。
生成后的結果如下:
/development/tools$ ls
makedict media.pk8 mkstubs platform.pk8 releasekey.pk8 shared.pk8 make_key media.x509.pem platform.x509.pem releasekey.x509.pem shared.x509.pem
*.pk8是生成的私鑰,而*.x509.pem是公鑰,生成時兩者是成對出現的.
另外,如果出現 openssl : relocation error :openssl : symbol ...嘗試用 sudo 執行命令, 問題解決了!
生成的密鑰需要放到build/target/product/security/目錄下面,因為之前系統里面寫的test-keys的密鑰就放在此處。我們是仿照系統的方法的方法放在此處,后面寫系統簽名剛好用到所以放到該文件夾最合適。
2、 make_key介紹
a)生成公鑰
openssl genrsa -3 -out testkey.pem 2048
其中-3是算法的參數,2048是密鑰長度,testkey.pem是輸出的文件
b) 轉成x509格式(含作者有效期等)
openssl req -new -x509 -key testkey.pem -out testkey.x509.pem -days 10000 -subj ‘/C=US/ST=California/L=MountainView/O=Android/OU=Android/CN=Android/emailAddress=android@android.com’
c) 生成私鑰
openssl pkcs8 -in testkey.pem -topk8 -outform DER -out testkey.pk8 -nocrypt
把的格式轉換成PKCS #8,這里指定了-nocryp,表示不加密,所以簽名時不用輸入密碼
三、 如何簽名
1、 對APK簽名命令:
signapk publickey.x509[.pem] privatekey.pk8 input.apk output.apk
如上面就可以寫為:java -jar signapk.jar releasekey.x509.pem releasekey.pk8 input.apk output.apk
簽名后,輸出文件的meta_inf目錄下生長如下文件:
MANIFEST.MF:對非文件夾非簽名文件的文件,逐個生成SHA1的數字簽名信息
CERT.SF:對Manifest文件,使用SHA1-RSA算法,用私鑰進行簽名。
CERT.RSA:CERT.RSA文件中保存了公鑰、所采用的加密算法等信息。
下圖是eclipse中簽名的過程。
2、 對recovery 簽名命令:
signapk –w publickey.x509[.pem] privatekey.pk8 input.zip output.zip
- w參數表示對整個文件進行簽名,目前看只是在制作recovery升級包時使用。
對升級包的簽名可參考:http://blog.163.com/yi_yixinyiyi/blog/static/136286889201132533759563/
另外一種對系統的簽名可以直接在源碼加入編譯的簽名的路徑不需要在去手動簽名,其修改方法如下:
主要參考了:http://www.cnblogs.com/leaven/p/3860583.html 講的很詳細,也很有用。
a)修改/build/core/config.mk中變量DEFAULT_SYSTEM_DEV_CERTIFICATE ,修改方法如下:(紅色為添加代碼)
# The default key if not set as LOCAL_CERTIFICATE
ifdef PRODUCT_DEFAULT_DEV_CERTIFICATE
DEFAULT_SYSTEM_DEV_CERTIFICATE := $(PRODUCT_DEFAULT_DEV_CERTIFICATE)
else
DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/testkey
endif
#modify by lran jzby 指出密鑰路徑
DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/releasekey
# ###############################################################
# Set up final options.
# ###############################################################
b) 在build/core/Makefile中添加:
(1)mx中修改方法
ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/product/security/testkey)
BUILD_KEYS := test-keys
else
BUILD_KEYS := dev-keys
endif
#modify by lran jzby 目的就是配置當前的名方式為release方式
ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/product/security/releasekey)
BUILD_KEYS := release-keys
endif
BUILD_VERSION_TAGS += $(BUILD_KEYS)
.....
ifeq ($(BUILD_SECURE),true)
OTA_PUBLIC_KEYS := device/*/$(TARGET_DEVICE)/releasekey.x509.pem
else
OTA_PUBLIC_KEYS := $(SRC_TARGET_DIR)/product/security/testkey.x509.pem
endif
#modify by lran jzby 指向密鑰
OTA_PUBLIC_KEYS := build/target/product/security/releasekey.x509.pem
(2)hisi中修改方法
ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/product/security/testkey)
BUILD_VERSION_TAGS += test-keys
endif
#modify by lran jzby
ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/product/security/releasekey)
BUILD_VERSION_TAGS += release-keys
else
BUILD_VERSION_TAGS += dev-keys
endif
BUILD_VERSION_TAGS := $(subst $(space),$(comma),$(sort $(BUILD_VERSION_TAGS)))
海斯中第二行不需要修改,因為海斯對應的該行以寫成OTA_PUBLIC_KEYS := $(DEFAULT_SYSTEM_DEV_CERTIFICATE).x509.pem可以直接被自動替換
(3),其實在安裝apk的package.mk中也有密鑰的使用,不過他和海斯一樣直接用DEFAULT_SYSTEM_DEV_CERTIFICATE替代了,所以不用修改。
ifeq ($(LOCAL_CERTIFICATE),)
LOCAL_CERTIFICATE := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
endif
......
private_key := $(LOCAL_CERTIFICATE).pk8
certificate := $(LOCAL_CERTIFICATE).x509.pem
(4)修改完之后就要編譯了,如果上面的這些key在制作的時候輸入了password就會出現如下錯誤:
Enter password for build/target/product/security/releasekey.pk8 (password will not be hidden): java.lang.NullPointerException
at com.android.signapk.SignApk.decryptPrivateKey(SignApk.java:142)
at com.android.signapk.SignApk.readPrivateKey(SignApk.java:166)
at com.android.signapk.SignApk.main(SignApk.java:531)
Enter password for build/target/product/security/releasekey.pk8 (password will not be hidden): make: *** [out/target/product/gotechcn/obj/APPS/CalendarProvider_intermediates/package.apk] 錯誤 1
make: *** 正在等待未完成的任務....
Enter password for build/target/product/security/releasekey.pk8 (password will not be hidden): java.lang.NullPointerException
at com.android.signapk.SignApk.decryptPrivateKey(SignApk.java:142)
at com.android.signapk.SignApk.readPrivateKey(SignApk.java:166)
at com.android.signapk.SignApk.main(SignApk.java:531)
make: *** [out/target/product/gotechcn/obj/APPS/Calculator_intermediates/package.apk] 錯誤 1
Warning: AndroidManifest.xml already defines minSdkVersion (in http://schemas.android.com/apk/res/android); using existing value in manifest.
Warning: AndroidManifest.xml already defines targetSdkVersion (in http://schemas.android.com/apk/res/android); using existing value in manifest.
'out/target/common/obj/APPS/Calendar_intermediates/classes.dex' as 'classes.dex'...
Enter password for build/target/product/security/releasekey.pk8 (password will not be hidden): java.lang.NullPointerException
at com.android.signapk.SignApk.decryptPrivateKey(SignApk.java:142)
at com.android.signapk.SignApk.readPrivateKey(SignApk.java:166)
at com.android.signapk.SignApk.main(SignApk.java:531)
make: *** [out/target/product/gotechcn/obj/APPS/Calendar_intermediates/package.apk] 錯誤 1
^Cmake: *** [out/target/product/gotechcn/obj/APPS/BasicDreams_intermediates/package.apk] 錯誤 130
其實會出現這個錯誤的最根本的原因是多線程的問題。在編譯的時候為了加速一般都會執行make -jxxx,這樣本來需要手動輸入密碼的時候,由於其它線程的運行,就會導致影響當前的輸入終端,所以就會導致密碼無法輸入的情況!再編譯完成之后也可以在build.prop中查看到變量:ro.build.tags=release-keys 這樣處理了之后編譯出來的都是簽名過的了,系統才算是release版本。我發現我這樣處理之后,整個系統的算是全部按照我的要求簽名了。
四、 簽名的相關文件
1、 apk包中簽名相關的文件在meta_INF目錄下
CERT.SF:生成每個文件相對的密鑰
MANIFEST.MF:數字簽名信息
xxx.SF:這是JAR文件的簽名文件,占位符xxx標識了簽名者
xxx.DSA:對輸出文件的簽名和公鑰
2、相關源碼
development/tools/jarutils/src/com.anroid.jarutils/SignedJarBuilder.java
frameworks/base/services/java/com/android/server/PackageManagerService.java
frameworks/base/core/java/android/content/pm/PackageManager.java
frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
dalvik/libcore/security/src/main/java/java/security/Sign*
build/target/product/security/platform.*
build/tools/signapk/*
五、 怎樣驗證簽名
1、 APK驗證:
安裝apk時,通過CERT.RSA查找公鑰和算法,並對CERT.SF進行解密和簽名驗證,確認MANIFEST.MF,最終對每個文件簽名校驗。
升級時,android也會進行簽名驗證。如果遇到以下情況,都不能完成升級:
a)兩個應用,名字相同,簽名不同
b)升級時前一版本簽名,后一版本沒簽名。
c)升級時前一版本為DEBUG簽名,后一個為自定義簽名。
d)升級時前一版本為Android源碼中的簽名,后一個為DEBUG簽名或自定義簽名。
e)安裝未簽名的程序。
f)安裝升級已過有效期的程序。
一般在apk安裝時提示出錯:INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES
a)兩個應用,名字相同,簽名不同
b)升級時前一版本簽名,后一版本沒簽名
c)升級時前一版本為DEBUG簽名,后一個為自定義簽名
d)升級時前一版本為Android源碼中的簽
以上可以看到,隨便更換密鑰對后簽名生成的apk也是可以安裝在任何平台的。因為公鑰是放在apk里的。
2、 RECOVERY:
Recovery公鑰是固定在代碼里面。並沒有采用CERT.RSA里面公鑰。目前幾乎所有的手機平台都是用testkey。固定公鑰目的可能是為了確保其它平台的升級包不能被升級。比如我們統一更換了一個密鑰對,那么recovery里面固定的公鑰更改后,用testkey做成的升級包是無法升級成功的。
同時recovery只是對整個升級壓縮文件進行簽名校驗(前面說過recovery簽名使用的是-w)。
Recovery 代碼里面使用的公鑰是按照rsapublickey 數據結構存儲。這個數據是通過dumppublickey工具從制作出來的x509證書文件中獲得。Dumppublickey代碼在網上可以查找到。