最近一直有两个疑问 升级包签名的规则和签名文件具体的位置,所以大概看了下签名流程并整理出来
大概理解了下 1.如何签名 2.如何校验
一.相关整理
首先要大概知道的两个内容
1.CMS数字签名
参考:https://www.ibm.com/developerworks/cn/java/j-lo-cms-ticketbasesso/
大概理解为
签名阶段:取数据的hash 使用私钥进行加密放入签名部分, 签名部分+数据部分 组合为新的zip
校验阶段:取出数据的hash,使用公钥对签名部分的加密hash进行解密,解密后的hash和数据的hash进行对比
2.zip文件的结构
参考:http://blog.sina.com.cn/s/blog_c496a6310102wje4.html
这里主要理解以下其中的End of central directory record
二.生成签名
做升级包的流程还是从ota_from_target_files ,这部分略过,直接从签名开始分析,传入的什么参数
最后的签名命令为:
java -Djava.library.path=out/host/linux-x86/lib64
-jar out/host/linux-x86/framework/signapk.jar
-w device/mediatek/security/releasekey.x509.pem ./device/mediatek/security/releasekey.pk8
update.zip update_sign.zip 2>&1 | tee sign.log
所以签名的流程从sign.jar开始,源码位于build/make/tools/signapk/src/com/android/signapk/SignApk.java
jar文件也是从main方法开始 开始上菜,第一道小炒肉
1.main方法
几句话总结
1.解析传入的参数 根据-w判断是否为签名升级包
2.获取输入和输出
3.获取公钥 转化为x509证书格式 readPublicKey方法
4.定义升级内文件将使用的时间戳 (这个其实是为了签名APK时 有点用处,官方文档中说可以减少patch的生成)
5.获取私钥 readPrivateKey
6.定义签名算法的类型 getDigestAlgorithmForOta
7.将以上参数传入签名升级包的方法signWholeFile
//从main方法开始
public static void main(String[] args) {
if (args.length < 4) usage();
//输出一波参数信息
//arg = -w
//arg = device/mediatek/security/releasekey.x509.pem
//arg = ./device/mediatek/security/releasekey.pk8
//arg = update.zip
//arg = update_sign.zip
for(String arg : args){
System.out.println("arg = " + arg);
}
// Install Conscrypt as the highest-priority provider. Its crypto primitives are faster than
// the standard or Bouncy Castle ones.
Security.insertProviderAt(new OpenSSLProvider(), 1);
// Install Bouncy Castle (as the lowest-priority provider) because Conscrypt does not offer
// DSA which may still be needed.
// TODO: Stop installing Bouncy Castle provider once DSA is no longer needed.
Security.addProvider(new BouncyCastleProvider());
boolean signWholeFile = false;
String providerClass = null;
int alignment = 4;
Integer minSdkVersionOverride = null;
boolean signUsingApkSignatureSchemeV2 = true;
//1.解析传入的参数 根据-w判断是否为签名升级包
int argstart = 0;
while (argstart < args.length && args[argstart].startsWith("-")) {
if ("-w".equals(args[argstart])) {
signWholeFile = true;
++argstart;
} else if ("-providerClass".equals(args[argstart])) {
if (argstart + 1 >= args.length) {
usage();
}
providerClass = args[++argstart];
++argstart;
} else if ("-a".equals(args[argstart])) {
alignment = Integer.parseInt(args[++argstart]);
++argstart;
} else if ("--min-sdk-version".equals(args[argstart])) {
String minSdkVersionString = args[++argstart];
try {
minSdkVersionOverride = Integer.parseInt(minSdkVersionString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"--min-sdk-version must be a decimal number: " + minSdkVersionString);
}
++argstart;
} else if ("--disable-v2".equals(args[argstart])) {
signUsingApkSignatureSchemeV2 = false;
++argstart;
} else {
usage();
}
}
//args.length = 5
//argstart = 1
System.out.println("args.length = " + args.length);
System.out.println("argstart = " + argstart);
//如果去掉开头的-w余数等于2 说明参数不对
if ((args.length - argstart) % 2 == 1) usage();
//这里numKeys其实是1
int numKeys = ((args.length - argstart) / 2) - 1;
//这里的打印也能看出来,当签名升级包的时候 签名只有一个
if (signWholeFile && numKeys > 1) {
System.err.println("Only one key may be used with -w.");
System.exit(2);
}
loadProviderIfNecessary(providerClass);
//2.获取输入和输出
//获取输入文件 和输出文件的名称 倒数第一个参数和倒数第二个
String inputFilename = args[args.length-2];
String outputFilename = args[args.length-1];
JarFile inputJar = null;
//定义文件流输出文件
FileOutputStream outputFile = null;
try {
//3.获取公钥文件releasekey.x509.pem
File firstPublicKeyFile = new File(args[argstart+0]);
//创建x509证书对象
X509Certificate[] publicKey = new X509Certificate[numKeys];
try {
for (int i = 0; i < numKeys; ++i) {
int argNum = argstart + i*2;
//将公钥转换为x509证书格式
publicKey[i] = readPublicKey(new File(args[argNum]));
System.out.println("publicKey" + "[" + i + "]" + publicKey[i]);
}
} catch (IllegalArgumentException e) {
System.err.println(e);
System.exit(1);
}
// Set all ZIP file timestamps to Jan 1 2009 00:00:00.
// 4.创建了一个时间戳 签名之后 文件内容时间为2009 00:00:00.
long timestamp = 1230768000000L;
// The Java ZipEntry API we're using converts milliseconds since epoch into MS-DOS
// timestamp using the current timezone. We thus adjust the milliseconds since epoch
// value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.
timestamp -= TimeZone.getDefault().getOffset(timestamp);
//5.获取私钥releasekey.pk8
PrivateKey[] privateKey = new PrivateKey[numKeys];
for (int i = 0; i < numKeys; ++i) {
int argNum = argstart + i*2 + 1;
//读取私钥内容
privateKey[i] = readPrivateKey(new File(args[argNum]));
System.out.println("privateKey" + "[" + i + "]" + privateKey[i]);
}
//将输入文件转换为了jar文件
inputJar = new JarFile(new File(inputFilename), false); // Don't verify.
outputFile = new FileOutputStream(outputFilename);
// NOTE: Signing currently recompresses any compressed entries using Deflate (default
// compression level for OTA update files and maximum compession level for APKs).
// 如果signWholeFile为true 使用signWholeFile方法
if (signWholeFile) {
//6.定义签名算法的类型
int digestAlgorithm = getDigestAlgorithmForOta(publicKey[0]);
//digestAlgorithm = 1
System.out.println("digestAlgorithm = " + digestAlgorithm);
// inputJar输入文件,也就是原始的升级包
// firstPublicKeyFile 公钥文件
// publicKey[0]x509证书格式
// privateKey[0] 解析后的PrivateKey私钥
// digestAlgorithm 签名算法的类型
// outputFile 输出文件
signWholeFile(inputJar, firstPublicKeyFile,
publicKey[0], privateKey[0], digestAlgorithm,
timestamp,
outputFile);
} else {
//后面的不用看了,签名APK用的
......
......
其中用到几个方法,大致过下
1.1 readPublicKey
private static X509Certificate readPublicKey(File file)
throws IOException, GeneralSecurityException {
FileInputStream input = new FileInputStream(file);
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
1.2 readPrivateKey
/** Read a PKCS#8 format private key.读取PKCS#8格式的私钥 */
private static PrivateKey readPrivateKey(File file)
throws IOException, GeneralSecurityException {
//创建数据输入流
DataInputStream input = new DataInputStream(new FileInputStream(file));
try {
//一次性把长度都读完
byte[] bytes = new byte[(int) file.length()];
input.read(bytes);
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. 检查这是否在EncryptedPrivateKeyInfo结构中*/
PKCS8EncodedKeySpec spec = decryptPrivateKey(bytes, file);
if (spec == null) {
spec = new PKCS8EncodedKeySpec(bytes);
}
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
* 现在,它处于PKCS#8 PrivateKeyInfo结构中。 阅读其算法OID并将其用于构造KeyFactory。
*/
PrivateKeyInfo pki;
try (ASN1InputStream bIn =
new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()))) {
pki = PrivateKeyInfo.getInstance(bIn.readObject());
}
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
System.out.println("algOid = " + algOid);
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
/**
* Decrypt an encrypted PKCS#8 format private key.
* 解密加密的PKCS#8格式的私钥。
* Based on ghstark's post on Aug 6, 2006 at
* http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
*
* @param encryptedPrivateKey The raw data of the private key 私钥的数据
* @param keyFile The file containing the private key 私钥文件
*/
private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)
throws GeneralSecurityException {
EncryptedPrivateKeyInfo epkInfo;
try {
epkInfo = new EncryptedPrivateKeyInfo(en

本文详细解析了安卓OTA升级包的签名流程,包括签名规则、签名文件位置及签名与校验的具体实现。从签名算法的选择、公钥与私钥的处理到CMS数字签名的生成,再到升级包的校验过程,全面覆盖了升级包安全性的核心技术。
最低0.47元/天 解锁文章
4927

被折叠的 条评论
为什么被折叠?



