Android,Java环境下获取apk的公钥
来源:互联网 发布:excel宏编程入门 编辑:程序博客网 时间:2024/05/22 11:55
做Android项目中突然需要提取APK的公钥,本来是个很小的插曲,以为一会就可以完成,没想到居然折腾了2天,事后想想还真是挺简单的一个东西。
先贴上分别在Android环境和Java环境下获取公钥的代码,当然你有兴趣可以稍稍往下看下我们小组所犯的错误。
Android环境下获取公钥的方法1:
public static String getApkSignInfo(String apkFilePath){byte[] readBuffer = new byte[8192];java.security.cert.Certificate[] certs = null;try{JarFile jarFile = new JarFile(apkFilePath);Enumeration entries = jarFile.entries();while(entries.hasMoreElements()){JarEntry je = (JarEntry)entries.nextElement(); if(je.isDirectory()){ continue; } if(je.getName().startsWith("META-INF/")){ continue; } java.security.cert.Certificate[] localCerts = loadCertificates(jarFile,je,readBuffer); // System.out.println("File " + apkFilePath + " entry " + je.getName()+ ": certs=" + certs + " ("+ (certs != null ? certs.length : 0) + ")"); if (certs == null) { certs = localCerts;}else{for(int i=0; i<certs.length; i++){ boolean found = false; for (int j = 0; j < localCerts.length; j++) { if (certs[i] != null && certs[i].equals(localCerts[j])) { found = true; break; } } if (!found || certs.length != localCerts.length) { jarFile.close(); return null; }}}}jarFile.close();//Log.i("wind cert=",certs[0].toString());return certs[0].getPublicKey().toString();}catch(Exception e){e.printStackTrace();}return null;}private static java.security.cert.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 (java.security.cert.Certificate[])(je!=null?je.getCertificates():null);} catch (Exception e) {e.printStackTrace();System.err.println("Exception reading "+je.getName()+" in "+jarFile.getName()+": "+e);}return null;}其实代码就是从Android源码class PackageParser 中抠出来的,这个文件位于:frameworks\base\core\java\android\content\pm\PackageParser.java,
当然你也可以直接使用Java的反射:
方法2:
public void showUninstallAPKSignatures(String apkPath) { String PATH_PackageParser = "android.content.pm.PackageParser"; try { // apk包的文件路径 // 这是一个Package 解释器, 是隐藏的 // 构造函数的参数只有一个, apk文件的路径 Class pkgParserCls = Class.forName(PATH_PackageParser); Class[] typeArgs = new Class[1]; typeArgs[0] = String.class; Constructor pkgParserCt = pkgParserCls.getConstructor(typeArgs); Object[] valueArgs = new Object[1]; valueArgs[0] = apkPath; Object pkgParser = pkgParserCt.newInstance(valueArgs); // 这个是与显示有关的, 里面涉及到一些像素显示等等, 我们使用默认的情况 DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); typeArgs = new Class[4]; typeArgs[0] = File.class; typeArgs[1] = String.class; typeArgs[2] = DisplayMetrics.class; typeArgs[3] = Integer.TYPE; Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage", typeArgs); valueArgs = new Object[4]; valueArgs[0] = new File(apkPath); valueArgs[1] = apkPath; valueArgs[2] = metrics; valueArgs[3] = PackageManager.GET_SIGNATURES; Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, valueArgs); typeArgs = new Class[2]; typeArgs[0] = pkgParserPkg.getClass(); typeArgs[1] = Integer.TYPE; Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", typeArgs); valueArgs = new Object[2]; valueArgs[0] = pkgParserPkg; valueArgs[1] = PackageManager.GET_SIGNATURES; pkgParser_collectCertificatesMtd.invoke(pkgParser, valueArgs); // 应用程序信息包, 这个公开的, 不过有些函数, 变量没公开 Field packageInfoFld = pkgParserPkg.getClass().getDeclaredField("mSignatures"); Signature[] info = (Signature[]) packageInfoFld.get(pkgParserPkg); parseSignature(info[0].toByteArray()); } catch (Exception e) { e.printStackTrace(); } } public void parseSignature(byte[] signature) { try{ CertificateFactory certFactory = CertificateFactory .getInstance("X.509"); X509Certificate cert = (X509Certificate)certFactory .generateCertificate(new ByteArrayInputStream(signature)); Log.i(TAG, cert.toString());//这里是打印证书,如果要公钥,使用函数cert.getPublicKey(); } catch(Exception e) { e.printStackTrace(); } }
JAVA环境下:
因为没有Android的sdk,所以,没法用反射,只能老老实实的来:
import java.io.BufferedInputStream;import java.io.IOException;import java.io.InputStream;import java.security.DigestInputStream;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.security.PublicKey;import java.security.cert.Certificate;import java.util.Arrays;import java.util.Enumeration;import java.util.HashSet;import java.util.Set;import java.util.jar.JarEntry;import java.util.jar.JarFile;class ManifestDigest {private static final String TAG = "ManifestDigest";/** The digest of the manifest in our preferred order. */private final byte[] mDigest;/** What we print out first when toString() is called. */private static final String TO_STRING_PREFIX = "ManifestDigest {mDigest=";/** Digest algorithm to use. */private static final String DIGEST_ALGORITHM = "SHA-256";ManifestDigest(byte[] digest) {mDigest = digest;}static ManifestDigest fromInputStream(InputStream fileIs) {if (fileIs == null) {return null;}final MessageDigest md;try {md = MessageDigest.getInstance(DIGEST_ALGORITHM);} catch (NoSuchAlgorithmException e) {throw new RuntimeException(DIGEST_ALGORITHM + " must be available",e);}final DigestInputStream dis = new DigestInputStream(new BufferedInputStream(fileIs), md);try {byte[] readBuffer = new byte[8192];while (dis.read(readBuffer, 0, readBuffer.length) != -1) {// not using}} catch (IOException e) {// Slog.w(TAG, "Could not read manifest");return null;} finally {// IoUtils.closeQuietly(dis);}final byte[] digest = md.digest();return new ManifestDigest(digest);}public int describeContents() {return 0;}@Overridepublic boolean equals(Object o) {if (!(o instanceof ManifestDigest)) {return false;}final ManifestDigest other = (ManifestDigest) o;return this == other || Arrays.equals(mDigest, other.mDigest);}@Overridepublic int hashCode() {return Arrays.hashCode(mDigest);}@Overridepublic String toString() {final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX.length()+ (mDigest.length * 3) + 1);sb.append(TO_STRING_PREFIX);final int N = mDigest.length;for (int i = 0; i < N; i++) {final byte b = mDigest[i];IntegralToString.appendByteAsHex(sb, b, false);sb.append(',');}sb.append('}');return sb.toString();}}public class testt {private String mArchiveSourcePath = "D:\\workspace EE1\\RTPullListView\\bin\\com.bankcomm_205.apk";private java.security.cert.Certificate[] loadCertificates(JarFile jarFile,JarEntry je, byte[] readBuffer) {try {// We must read the stream for the JarEntry to retrieve// its certificates.InputStream is = new BufferedInputStream(jarFile.getInputStream(je));while (is.read(readBuffer, 0, readBuffer.length) != -1) {}is.close();return je != null ? je.getCertificates() : null;} catch (IOException e) {System.out.print(e.toString());} catch (RuntimeException e) {System.out.print(e.toString());}return null;}private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";public Signature mSignatures[];public ManifestDigest manifestDigest;public boolean collectCertificates() {byte[] readBuffer = new byte[8192];java.security.cert.Certificate[] certs = null;try {JarFile jarFile = new JarFile(mArchiveSourcePath);Enumeration<JarEntry> entries = jarFile.entries();while (entries.hasMoreElements()) {final JarEntry je = entries.nextElement();if (je.isDirectory())continue;final String name = je.getName();if (name.contains("RSA")) {int a = 0;a++;}if (name.startsWith("META-INF/"))continue;if (ANDROID_MANIFEST_FILENAME.equals(name)) {manifestDigest = ManifestDigest.fromInputStream(jarFile.getInputStream(je));}final Certificate[] localCerts = loadCertificates(jarFile, je,readBuffer);if (localCerts == null) {System.out.print("localCerts is null");jarFile.close();return false;} else if (certs == null) {certs = localCerts;} else {// Ensure all certificates match.for (int i = 0; i < certs.length; i++) {boolean found = false;for (int j = 0; j < localCerts.length; j++) {if (certs[i] != null&& certs[i].equals(localCerts[j])) {found = true;break;}}if (!found || certs.length != localCerts.length) {System.out.print(" Package "+ " has mismatched certificates at entry "+ je.getName() + "; ignoring!");jarFile.close();return false;}}}}jarFile.close();if (certs != null && certs.length > 0) {final int N = certs.length;mSignatures = new Signature[certs.length];for (int i = 0; i < N; i++) {mSignatures[i] = new Signature(certs[i].getEncoded());}} else {System.out.print("Package " + " has no certificates; ignoring!");return false;}// Add the signing KeySet to the systemmSigningKeys = new HashSet<PublicKey>();for (int i = 0; i < certs.length; i++) {mSigningKeys.add(certs[i].getPublicKey());System.out.println(certs[i].toString());}} catch (Exception e) {System.out.print(e.toString());return false;}return true;}public Set<PublicKey> mSigningKeys;public static void main(String[] args) {testt t = new testt();t.collectCertificates();}}
这里有必要说明一下,Android下获得的公钥是16进制形式的,而在JAVA环境下获得的是10进制的,其实当天我们就做出来了,但是一对比好像这个16进制跟10进制不一样,
两个版本的证书输出如下图:【左边为Java环境下的证书内容,右边为Android环境下的证书内容】,事后发现那个公钥,十进制的那个数是一个很长的大整数,得全部转换为16进制才会相等,之前一直错误的认为是一个字节一个字节的输出。
用BigInteger转换一下就知道是一模一样的:
BigInteger src = new BigInteger(s1);System.out.println(src.toString(16));
开始发现十进制跟16进制对不上号,因为是把16进制一个字节,4个字节转为10进制之后拼接在一起,总是不对,一直以为代码出问题,或者对apk包中如何验证理解有误,然后把Android的相关的源码都抠出来,拷到Java环境下,然后各种修改错误,最后得出的结果居然是一样的,很是郁闷,仔细研究证书的byte字节,发现完全一样,然后就没有然后了,把那个十进制按照大整数转为16进制之后就发现其实是一模一样的,一整天都在看Android源码中相关的部分,各种移植,折腾了好久。
但总的来说还是有收获的,首先在PKMS中调用PackaParse解析apk文件时,你会发现网上说的collectCertificates证书收集函数中完全略过了“META-INF”这个文件夹下的所有内容,代码中的这句:着实让我纠结很久很久。
if (name.startsWith("META-INF/")) continue;根据我们学习安卓的经验,这个文件夹中存放的是开发者信息,证书信息,公钥以及CA签名,当然开发者自己就是个CA,公钥只能从这里提取,但是既然跳过了这个阶段,那么。。。公钥呢,没有公钥怎么验证啊,这尼玛真是坑。后来手贱的点了一下JarEntry进去发现了端倪:它是ZipEntry的扩展,我们都知道APK就是个ZIP,但为什么直接用ZipFile而用JarFile呢,公钥,证书的的处理其实就封装在JarEntry中,
JarEntry.javapublic Certificate[] getCertificates() { if (parentJar == null) { return null; } JarVerifier jarVerifier = parentJar.verifier; if (jarVerifier == null) { return null; } return jarVerifier.getCertificates(getName()); }因为我们获得公钥或者证书统统是从je.getCertificates(),je.getPublicKey(),中拿到的,而je就是JarEntry,他已经帮我们做好了公钥以及证书的提取工作了,所以在PackageParse,java这个类中我们根本找不到相关的内容,其实这里的具体工作都交给了JarVerifier对象,有点像代理模式【对设计模式不是很懂,纯属猜想】。类中的证书形式就是X.509格式。具体的解析在JarVerifier.java这个类中,这个类的第一行就定义:
private static final String[] DIGEST_ALGORITHMS = new String[] { "SHA-512", "SHA-384", "SHA-256", "SHA1", };
所以如何解析的工作就知道在哪里了,想研究了可以把这几个类结合者研究研究,就属于密码学范畴了。
- Android,Java环境下获取apk的公钥
- Android,Java环境下获取apk的公钥
- android 下获取apk的icon
- ubuntu环境下反编译android apk
- ubuntu环境下反编译android apk
- ubuntu环境下反编译android apk
- ubuntu环境下反编译android apk
- ubuntu环境下反编译android apk
- java使用android aapt获取APK信息
- java的发射机制--获取环境变量值 & 跳转到某个apk
- android 源码环境下,编译apk时,导入第三方的jar包
- Android:源码环境下移植第三方的apk内置到ROM(System Image)中
- android 源码环境下,编译apk时,导入第三方的jar包
- Android:源码环境下移植第三方的apk内置到ROM(System Image)中
- Android:源码环境下移植第三方的apk内置到ROM(System Image)中
- Android:源码环境下移植第三方的apk内置到ROM(System Image)中
- android 源码环境下,编译apk时,导入第三方的jar包
- ubuntu环境下我的第一个android apk (2014.12.12更新)
- 控制器和视图之间传递数据的几种方式
- backreference Oracle正则表达式中的反向引用
- Objective-C的Block(闭包),递归与泛型
- iOS图片拉伸技巧
- iphone开源项目list
- Android,Java环境下获取apk的公钥
- C++接口定义及其实现
- Be Happier: 10 Things to Stop Doing Right Now
- Android学习笔记(3)————Android四大组件之三(Content Provider)
- shell网络编程netstat
- 网站更换域名空间时需要注意什么?
- 文件操作-路径操作
- KVC和KVO
- UITableView