Android签名验证原理解析
来源:互联网 发布:联通网络电视客户端 编辑:程序博客网 时间:2024/05/18 18:21
一、基本概念
1、keytool
keytool 是个密钥和证书管理工具。它使用户能够管理和生成自己的公钥/私钥对及相关证书
2、keystore
keystore是一个密钥库,里面存放着一个一个的密钥对实体。也就是说密钥对是存放在keystore里面。
二、签名文件keystore的生成过程
使用keytool工具
keytool -genkeypair -alias "test" -keyalg "RSA" -keystore "test.keystore"
上面命令的作用就是创建一个别名为test的证书,该证书存放在名为test.keystore的密钥库中,若test.keystore密钥库不存在则创建。
下面我们来看看具体的源码实现(KeyTool.java):
// 如果使用-genkeypair指令if (command == GENKEYPAIR) { if (keyAlgName == null) { keyAlgName = "DSA"; } doGenKeyPair(alias, dname, keyAlgName, keysize, sigAlgName); kssave = true;}
从上面代码可以看出,会调用doGenKeyPair方法来产生一个密钥对。
private void doGenKeyPair(String alias, String dname, String keyAlgName, int keysize, String sigAlgName)throws Exception{ //1、创建一个密钥对生成对象 CertAndKeyGen keypair = new CertAndKeyGen(keyAlgName, sigAlgName, providerName); X500Name x500Name = new X500Name(dname); // 2、生成一个密钥对 keypair.generate(keysize); PrivateKey privKey = keypair.getPrivateKey(); // 3、生成一个证书链 X509Certificate[] chain = new X509Certificate[1]; chain[0] = keypair.getSelfCertificate( x500Name, getStartDate(startDate), validity*24L*60L*60L); if (keyPass == null) { keyPass = promptForKeyPass(alias, null, storePass); } // 创建一个密钥对实体,并将其放入keystore中 keyStore.setKeyEntry(alias, privKey, keyPass, chain);}
(1)、创建一个密钥对生成对象
public CertAndKeyGen (String keyType, String sigAlg, String providerName) throws NoSuchAlgorithmException, NoSuchProviderException{ if (providerName == null) { keyGen = KeyPairGenerator.getInstance(keyType); } else { try { keyGen = KeyPairGenerator.getInstance(keyType, providerName); } catch (Exception e) { // try first available provider instead keyGen = KeyPairGenerator.getInstance(keyType); } } this.sigAlg = sigAlg;}
可以看到里面封装的就是一个KeyPairGenerator对象,KeyPairGenerator就是一个密钥对生成类
(2)、生成一个密钥对
public void generate (int keyBits)throws InvalidKeyException{ KeyPair pair; try { if (prng == null) { prng = new SecureRandom(); } keyGen.initialize(keyBits, prng); pair = keyGen.generateKeyPair(); } catch (Exception e) { throw new IllegalArgumentException(e.getMessage()); } publicKey = pair.getPublic(); privateKey = pair.getPrivate();}
上面就是使用KeyPairGenerator对象来生成一个密钥对。密钥对包含有公钥和私钥。
(3)、生成一个证书链
public X509Certificate getSelfCertificate ( X500Name myname, Date firstDate, long validity)throws CertificateException, InvalidKeyException, SignatureException,NoSuchAlgorithmException, NoSuchProviderException{ X509CertImpl cert; Date lastDate; try { lastDate = new Date (); lastDate.setTime (firstDate.getTime () + validity * 1000); CertificateValidity interval = new CertificateValidity(firstDate,lastDate); X509CertInfo info = new X509CertInfo(); info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber( new java.util.Random().nextInt() & 0x7fffffff)); AlgorithmId algID = AlgorithmId.getAlgorithmId(sigAlg); info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algID)); info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(myname)); // 将生成的公钥放入证书里面 info.set(X509CertInfo.KEY, new CertificateX509Key(publicKey)); info.set(X509CertInfo.VALIDITY, interval); info.set(X509CertInfo.ISSUER, new CertificateIssuerName(myname)); cert = new X509CertImpl(info); cert.sign(privateKey, this.sigAlg); return (X509Certificate)cert; } catch (IOException e) { throw new CertificateEncodingException("getSelfCert: " + e.getMessage()); }}
上面就是利用给的的信息,生成一个证书X509Certificate,需要注意的是证书中包含有生成的公钥。也就是说我们的公钥是公开的,私钥是自己保存。
从上面的分析可以知道,在签名文件keystore中包含有一个密钥实体,密钥实体中包含有私钥和证书信息,在证书中包含有公钥信息。
二、APK签名过程
Android中签名有两个工具:jarsign和signapk
jarsign是Java本生自带的一个工具,他可以对jar进行签名的,它使用的是keystore文件来进行签名
signapk是专门为Android应用程序apk进行签名的工具,它使用的是.pk8和.x509.pem这两个文件进行签名,k8是私钥文件,x509.pem是含有公钥的文件。
下面主要来看看jarsign的签名过程,因为它使用的是keystore文件(JarSigner.java)
1、初始化过程
public static void main(String args[]) throws Exception { JarSigner js = new JarSigner(); js.run(args);}public void run(String args[]) { // 1、处理传入的参数 parseArgs(args); // 2、加载keystore文件 loadKeyStore(keystore, true); // 3、获取相关信息 getAliasInfo(alias); // 4、进行签名 signJar(jarfile, alias, args);}
(1)、处理传入的参数
主要对传入的参数进行处理
(2)加载keystore文件
KeyStore store = KeyStore.getInstance(storetype, providerName);store.load(is, storepass);
得到一个KeyStore对象,并且载入keystore文件
(3)获取相关信息
key = store.getKey(alias, storepass);privateKey = (PrivateKey)key;certChain = new X509Certificate[cs.length];for (int i=0; i<cs.length; i++) { certChain[i] = (X509Certificate)cs[i];}
这里主要获得到了密钥信息和证书信息,这个也是keystore存放的主要信息。
(4)进行签名
MANIFEST.MF文件生成
zipFile = new ZipFile(jarName);BASE64Encoder encoder = new JarBASE64Encoder();Vector<ZipEntry> mfFiles = new Vector<>();boolean wasSigned = false;for (Enumeration<? extends ZipEntry> enum_=zipFile.entries(); enum_.hasMoreElements();) { ZipEntry ze = enum_.nextElement(); if (!ze.isDirectory()) { // Add entry to manifest Attributes attrs = getDigestAttributes(ze, zipFile, digests, encoder); mfEntries.put(ze.getName(), attrs); mfModified = true; }}private Attributes getDigestAttributes(ZipEntry ze, ZipFile zf, MessageDigest[] digests, BASE64Encoder encoder)throws IOException { String[] base64Digests = getDigests(ze, zf, digests, encoder); Attributes attrs = new Attributes(); for (int i=0; i<digests.length; i++) { attrs.putValue(digests[i].getAlgorithm()+"-Digest", base64Digests[i]); } return attrs;}
逐一遍历APK中的所有条目,如果是目录就跳过,如果是一个文件,就用SHA1(或者SHA256)消息摘要算法提取出该文件的摘要然后进行BASE64编码后,作为“SHA1-Digest”属性的值写入到MANIFEST.MF文件中的一个块中。另外该块有一个“Name”属性,其值就是该文件在apk包中的路径。
CERT.SF文件生成
Map<String,Attributes> entries = sf.getEntries();Iterator<Map.Entry<String,Attributes>> mit = mf.getEntries().entrySet().iterator(); while(mit.hasNext()) { Map.Entry<String,Attributes> e = mit.next(); String name = e.getKey(); mde = md.get(name, false); if (mde != null) { Attributes attr = new Attributes(); for (int i=0; i < digests.length; i++) { attr.putValue(digests[i].getAlgorithm()+"-Digest", encoder.encode(mde.digest(digests[i]))); } entries.put(name, attr); }}
逐条计算MANIFEST.MF文件中每一个块的SHA1,并经过BASE64编码后,记录在CERT.SF中的同名块中,属性的名字是“SHA1-Digest
CERT.RSA文件生成
这个是我们重点关注的内容,因为我们的签名证书就是在这个地方发挥作用的。
public byte[] generateSignedData(ContentSignerParameters parameters, boolean omitContent, boolean applyTimestamp) throws NoSuchAlgorithmException, CertificateException, IOException { String signatureAlgorithm = parameters.getSignatureAlgorithm(); String keyAlgorithm = AlgorithmId.getEncAlgFromSigAlg(signatureAlgorithm); String digestAlgorithm = AlgorithmId.getDigAlgFromSigAlg(signatureAlgorithm); AlgorithmId digestAlgorithmId = AlgorithmId.get(digestAlgorithm); X509Certificate[] signerCertificateChain = parameters.getSignerCertificateChain(); Principal issuerName = signerCertificateChain[0].getIssuerDN(); BigInteger serialNumber = signerCertificateChain[0].getSerialNumber(); byte[] content = parameters.getContent(); ContentInfo contentInfo; contentInfo = new ContentInfo(content); byte[] signature = parameters.getSignature(); SignerInfo signerInfo = null; signerInfo = new SignerInfo((X500Name)issuerName, serialNumber, digestAlgorithmId, AlgorithmId.get(keyAlgorithm), signature); SignerInfo[] signerInfos = {signerInfo}; AlgorithmId[] algorithms = {digestAlgorithmId}; // Create the PKCS #7 signed data message PKCS7 p7 = new PKCS7(algorithms, contentInfo, signerCertificateChain, null, signerInfos); ByteArrayOutputStream p7out = new ByteArrayOutputStream(); p7.encodeSignedData(p7out); return p7out.toByteArray();}
它会把前面生成的 CERT.SF文件用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。CERT.RSA是一个满足PKCS7格式的文件。
详细的过程可以参考文章:Android签名机制之—签名过程详解
三、APK安装校验过程
1、通过在CERT.RSA文件中记录的签名信息,验证了CERT.SF没有被篡改过
libcore\luni\src\main\java\org\apache\harmony\security\utils\JarUtils.java
// InputStream signature 对应 CERT.SF文件// InputStream signatureBlock 对应 CERT.RSA文件public static Certificate[] verifySignature(InputStream signature, InputStream signatureBlock) throws IOException, GeneralSecurityException { BerInputStream bis = new BerInputStream(signatureBlock); ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis); SignedData signedData = info.getSignedData(); Collection<org.apache.harmony.security.x509.Certificate> encCerts = signedData.getCertificates(); if (encCerts.isEmpty()) { return null; } X509Certificate[] certs = new X509Certificate[encCerts.size()]; int i = 0; for (org.apache.harmony.security.x509.Certificate encCert : encCerts) { certs[i++] = new X509CertImpl(encCert); } List<SignerInfo> sigInfos = signedData.getSignerInfos(); SignerInfo sigInfo; if (!sigInfos.isEmpty()) { sigInfo = sigInfos.get(0); } else { return null; } // Issuer X500Principal issuer = sigInfo.getIssuer(); // Certificate serial number BigInteger snum = sigInfo.getSerialNumber(); // Locate the certificate int issuerSertIndex = 0; for (i = 0; i < certs.length; i++) { if (issuer.equals(certs[i].getIssuerDN()) && snum.equals(certs[i].getSerialNumber())) { issuerSertIndex = i; break; } } sig = Signature.getInstance(alg); // 这里实质会使用证书里面的公钥进行初始化 sig.initVerify(certs[issuerSertIndex]); List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes(); byte[] sfBytes = new byte[signature.available()]; signature.read(sfBytes); sig.update(sfBytes); // 使用签名进行验证 if (!sig.verify(sigInfo.getEncryptedDigest())) { throw new SecurityException("Incorrect signature"); } return createChain(certs[issuerSertIndex], certs);}
这里就是使用CERT.RSA文件中保存的对CERT.SF文件的签名信息和公钥信息来对当前的CERT.SF文件进行验证。
关于签名验证过程,如果不明白可以参考文章:
JAVA RSA密钥对的生成与验证
Java&keytool生成RSA密钥
2、通过CERT.SF文件中记录的摘要值,验证了MANIFEST.MF没有被修改过
3、apk内文件的摘要值要与MANIFEST.MF文件中记录的一致
后面两个思路估计简单,这里省略,具体参考:Android应用程序签名验证过程分析
- Android签名验证原理解析
- Android签名验证简介
- Android 签名验证
- Android 签名验证机制
- Android签名验证简介
- 破解android签名验证
- Android签名验证简介
- Android验证apk签名
- android JNI 验证签名
- android 签名验证
- Android签名原理
- android APK签名原理
- Android apk 签名原理
- Android apk签名原理
- Android 应用 签名原理
- Android签名机制原理
- Android 获取ROOT权限原理介绍和签名验证原理及反编译学习
- android jni签名验证(一)
- KazaQ's Socks
- 面试or笔试2——等概率生成0,1
- jsp的简单了解,6中JSP页面元素的简单应用
- MYSQL触发器T-SQL
- Beginning Spring学习笔记——第5章(一)ORM和JPA基础
- Android签名验证原理解析
- HashSet,TreeSet,LinkedHashSet 的用法和区别
- 剑指offer(十三)调整数组顺序使奇数位于偶数前面
- 在有序但含有空的数组中查找字符串
- ZooKeeper原理浅析
- 拆分PDF文档需要什么软件?如何进行操作?
- C++中类模板和实现分离的方法
- 动态SQL
- get方式长度受限,如何将较长数据传递到后台