android签名机制

android中所有运行在设备上的应用都必须要签名,这是系统提供的一种安全认证机制。
下面以问答形式来自我认识下,后面在根据部分内容来进一步研究:

1.为什么用签名机制?如果不签名呢?

为了防止app被反编译替换原有的程序,如果有相同包名的应用存在,新的apk则不能安装在设备上。   就算被反编译,在没有原有私钥的情况,重新签名生成的apk和原来的apk的标志是不同的。如此,被修改的apk如果安装在原来的设备上,则会提示:
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]

如果不签名呢?
直接安装到设备上,则也提示:

从上面可以有个很简易的认知,如果不签名或者签名不对,则不能安装在设备上。

如果应用androidmenifast.xml中加了android:sharedUserId="android.uid.system"时,要用平台签名,否则提示
   
更多安装异常可以查找相关信息。

2.平台签名?签名有很多种么?

有很多种。在android源码目录build\target\product\security,有如下几种签名密钥。
 
*.pk8表示的是private key---私钥
*.x509.pem理解为公钥

这里的platform.*即为平台签名的私钥和公钥,加密方式是非对称加密。
从上图可以看出有好几种的签名,4种具体表示可参考当前目录下的README.

?
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
The following commands were used to generate the test key pairs:
//制作key的脚本和相关信息
   development/tools/make_key testkey  '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
   development/tools/make_key platform '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
   development/tools/make_key shared   '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
   development/tools/make_key media    '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
 
The following standard test keys are currently included:
 
//testkey--当应用没有指定其他key时,源码默认以此key签名
testkey -- a generic key for packages that do not otherwise specify a key.
//平台签名,系统级应用用此签名
platform -- a test key for packages that are part of the core platform.
//当需要用到联系人的共享数据时,需要此签名
shared -- a test key for things that are shared in the home/contacts process.
//对系统的media/download相关的应用,用此签名
media -- a test key for packages that are part of the media/download system.
 
These test keys are used strictly in development, and should never be assumed
to convey any sort of validity.  When $BUILD_SECURE= true , the code should not
honor these keys in any context.
 
 
signing using the openssl commandline ( for boot/system images)
--------------------------------------------------------------
 
1 . convert pk8 format key to pem format
    % openssl pkcs8 -inform DER -nocrypt -in testkey.pk8 -out testkey.pem
 
2 . create a signature using the pem format key
    % openssl dgst -binary -sha1 -sign testkey.pem FILE > FILE.sig
 
extracting public keys for embedding
------------------------------------
it's a Java tool
but it generates C code
take a look at commands/recovery/Android.mk
you'll see it running $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar

从shared签名,可以联想到:
当A应用需要用到B应用的共享数据时,他们的
android:sharedUserId="xxx"需要一致,签名也需要一致

注:当应用工程的Android.mk中未指定LOCAL_CERTIFICATE时,源码编译则使用默认testkey


3.如何区别签名过和没签名的apk?

注:apk支持多次签名,以最后一次为准
apk是个zip包格式,这里就有个很简单直观的方式---用压缩程序方式打开apk,看下图:
 
签名过的apk包里有个META-INF文件夹,里面就是对各个资源文件用SHA1加密后的值。

4.了解加密、数字摘要、数字签名和数字证书


那么应该如何签名呢?

apk签名有几种方式:

1.源码编译签名

源码下编译的apk,系统会默认签名.

2.命令行签名

1>添加权限

   很多应用会需要提升系统用户组权限,所以要在AndroidManifest.xml文件下添加    

   android:sharedUserId="android.uid.system" 。

2>在Eclipse中导出无签名的应用文件

   在工程中:右键->Android Tools -> Export Unsigned Application Package导出应用

3>找出系统签名密钥

   系统密钥为: platform.pk8和platform.x509.pem

   路径: build\target\product\security 

4>找出系统签名工具  

   工具为:signApk.jar 

   路径:/out/host/linux-x86/framework/ signApk.jar 

 

5>开始签名

  将第2、3、4步找到的无签名应用、platform.pk8、platform.x509.pem和signApk.jar放到同一文件夹下如F:\sign。

  打开 dos 操作界面,定们到F:\sign,输入命令:

  java -jar  signapk.jar  platform.x509.pem  platform.pk8 **.apk   ***.apk 

(**.apk 为未签名应用  ***.apk 为签名之后应用)

笔者实际中做的一般都是系统级应用开发,一般都用这种签名,比较方便,推荐下


3.eclipse签名

eclipse run应用调试时,在/bin/*.apk下生成eclipse默认签名的apk,使用的签名密钥位于
    C:\Documents and Settings\Administrator\.android\debug.keystore.
普通应用可以安装到设备中,但
如果应用要获取系统用户组时,加上了 android:sharedUserId="android.uid.system",
则此种签名的应用则无效。会提示


注:测试时可用此种方式直接run应用调试,但是发布时切忌不可用eclipse的默认签名run出的bin/*.apk,原因:
1)eclipse的默认debug签名有效期只有1年,并且不能再android market中上架。
2)debug.keystore在不同的机器上所生成的可能都不一样,当如果此debug.keysotre由于特殊原因丢失,那之前发的应用都无法更新、替换,是非常严重的问题



制作签名

一、制作android签名公钥/密钥

1.生成长度为2048位的RSA私钥

openssl genrsa -3 -out test.pem 2048

2.生成x509格式的公钥证书
openssl req -new -x509 -key test.pem -out test.x509.pem -days 10000

3.生成符合PKCS8标注的私钥文件
openssl pkcs8 -in test.pem -topk8 -outform DER -out test.pk8 -nocrypt


二、系统平台签名转eclipse签名

1.android 源码目录build\target\product\security 取platform.pk8 platform.x509.pem放到一个目录下

E:\sign\convert_keystore>ls

platform.pk8 platform.x509.pem

2.把pkcs8格式的私钥转换成pkcs12格式:

E:\sign\convert_keystore>openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out shared.priv.pem -nocrypt

E:\sign\convert_keystore>ls

shared.priv.pem platform.pk8 platform.x509.pem


3.把x509.pem公钥转换成pkcs12格式:

E:\sign\convert_keystore> openssl pkcs12 -export -in platform.x509.pem -inkey shared.priv.pem -out shared.pk12 -name androiddebugkey

Enter Export Password: (密码android)

Verifying - Enter Export Password:(密码android)


E:\sign\convert_keystore>ls

shared.priv.pem platform.pk8 platform.x509.pem shared.pk12


4.生成debug.keystore

E:\sign\convert_keystore>keytool -importkeystore -deststorepass android -destkeypass android -destkeystore debug.keystore -srckeystore shared.pk12 

-srcstoretype PKCS12 -srcstorepass android -alias androiddebugkey 

E:\sign\convert_keystore>ls

convert debug.keystore platform.pk8 platform.x509.pem shared.pk12 shared.priv.pem


5.eclipse 的Windows/Preferences/Android/Build中设置"Custom debug keystore"为刚才生成的keystore即可


签名过程简析

先看张简易的时序图:
 

主要对签名时的命令行进行分析: 
java -jar  signapk.jar  platform.x509.pem  platform.pk8 **.apk   ***.apk     
注:如果对已签名的apk执行该命令,会重新覆盖已有的签名信息,故印证前面说讲的支持多次签名,以最后一次为准

signapk代码位于android源码路径:
SignApk.java (build\tools\signapk) 

是个可执行程序,代码只有514行,具体可参考代码。

?
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
public static void main(String[] args) {
         if (args.length != 4 && args.length != 5 ) {
             System.err.println( "Usage: signapk [-w] " +
                     "publickey.x509[.pem] privatekey.pk8 " +
                     "input.jar output.jar" );
             System.exit( 2 );
         }
 
         boolean signWholeFile = false ;
         int argstart = 0 ;
         if (args[ 0 ].equals( "-w" )) {
             signWholeFile = true ;
             argstart = 1 ;
         }
 
         JarFile inputJar = null ;
         JarOutputStream outputJar = null ;
         FileOutputStream outputFile = null ;
 
         try {
             X509Certificate publicKey = readPublicKey( new File(args[argstart+ 0 ]));
 
             // Assume the certificate is valid for at least an hour.
             long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000 ;
 
             PrivateKey privateKey = readPrivateKey( new File(args[argstart+ 1 ]));
             inputJar = new JarFile( new File(args[argstart+ 2 ]), false );  // Don't verify.
 
             OutputStream outputStream = null ;
             if (signWholeFile) {
                 outputStream = new ByteArrayOutputStream();
             } else {
                 outputStream = outputFile = new FileOutputStream(args[argstart+ 3 ]);
             }
             outputJar = new JarOutputStream(outputStream);
             outputJar.setLevel( 9 );
 
             JarEntry je;
 
             // MANIFEST.MF
             Manifest manifest = addDigestsToManifest(inputJar);
             je = new JarEntry(JarFile.MANIFEST_NAME);
             je.setTime(timestamp);
             outputJar.putNextEntry(je);
             manifest.write(outputJar);
 
             // CERT.SF
             Signature signature = Signature.getInstance( "SHA1withRSA" );
             signature.initSign(privateKey);
             je = new JarEntry(CERT_SF_NAME);
             je.setTime(timestamp);
             outputJar.putNextEntry(je);
             writeSignatureFile(manifest,
                     new SignatureOutputStream(outputJar, signature));
 
             // CERT.RSA
             je = new JarEntry(CERT_RSA_NAME);
             je.setTime(timestamp);
             outputJar.putNextEntry(je);
             writeSignatureBlock(signature, publicKey, outputJar);
 
             // Everything else
             copyFiles(manifest, inputJar, outputJar, timestamp);
 
             outputJar.close();
             outputJar = null ;
             outputStream.flush();
 
             if (signWholeFile) {
                 outputFile = new FileOutputStream(args[argstart+ 3 ]);
                 signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),
                                     outputFile, publicKey, privateKey);
             }
         } catch (Exception e) {
             e.printStackTrace();
             System.exit( 1 );
         } finally {
             try {
                 if (inputJar != null ) inputJar.close();
                 if (outputFile != null ) outputFile.close();
             } catch (IOException e) {
                 e.printStackTrace();
                 System.exit( 1 );
             }
         }
     }

1.MANIFEST.MF:保存资源文件的摘要经base64编码后的信息---数字摘要
2.CERT.SF:保存对摘要hash后的信息----数字签名
3.CERT.RSA:保存公钥信息---数字证书

涉及到的类:
Signature.java (frameworks\base\core\java\android\content\pm)


之后设备安装apk,会校验签名,校验过程主要在PackageManagerService中实现


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值