1. 概要

如果进行过OTA升级的开发者,都或多或少有这样的疑问,如何确定该OTA升级包是可以信任的呢?这其中其实涉及到一个签名验证的流程。

2. 签名生成

在生成正规的固件时,一般会运行生成新key的脚本,并重新修改key中的信息。以网上常用的生成key的脚本为例:

#!/bin/sh

AUTH='/C=CN/ST=xxxx/L=xxxxx/O=xxxxxx/OU=xxxxx/CN=China/emailAddress=xxxxxxx@com'

openssl genrsa -3 -out $1.pem 2048

openssl req -new -x509 -key $1.pem -out $1.x509.pem -days 10000 \

-subj "$AUTH"

echo "Please enter the password for this key:"

openssl pkcs8 -in $1.pem -topk8 -outform DER -out $1.pk8 -passout stdin

其中openssl通过genrsa标准命令生成私钥,默认大小为2048:

openssl genrsa -3 -out $1.pem 2048

生成私钥后,生成证书签署请求,即公钥:

openssl req -new -x509 -key $1.pem -out $1.x509.pem -days 365 \

-subj "$AUTH"

-new: new request

-x509: output a x509 structure instead of a cert. req.该选项说明生成一个自签名的证书。

-keyfile: use the private key contained in file

-days: number of days a certificate generated by -x509 is valid for.

最后通过私钥pem文件生成PKCS8私钥文件

openssl pkcs8 -in $1.pem -topk8 -outform DER -out $1.pk8 -passout stdin

也可以参考android原生的生成key的流程,位于android/development/tools下的make_key脚本i。

自此后,生成了一对公私钥用于签名校验。

3. sign_target_files_apks.py

3.1 对Apk进行重签名流程

sign_target_files_apks在本人之前的用法都局限于将targetfile里的apk进行重签名,其流程如下:

1.获取脚本输入参数

2.获取输入文件以及输出文件参数,读取misc_info文件

input_zip = zipfile.ZipFile(args[0], "r")

output_zip = zipfile.ZipFile(args[1], "w")

misc_info = common.LoadInfoDict(input_zip)

misc_info记录了一些参数,如下:

其中这里关注的是默认签名的路径:

default_system_dev_certificate=build/target/product/security/testkey

3.建立key映射

假如在调用脚本时未指定-d.-k参数,那么默认使用的正是系统自带的testkey。否则,将会映射到指定key目录下的Key

def BuildKeyMap(misc_info, key_mapping_options):

for s, d in key_mapping_options:

if s is None: # -d option

devkey = misc_info.get("default_system_dev_certificate",

"build/target/product/security/testkey")

devkeydir = os.path.dirname(devkey)

OPTIONS.key_map.update({

devkeydir + "/testkey": d + "/releasekey",

devkeydir + "/devkey": d + "/releasekey",

devkeydir + "/media": d + "/media",

devkeydir + "/shared": d + "/shared",

devkeydir + "/platform": d + "/platform",

})

else:

OPTIONS.key_map[s] = d

4.读取targetfile中的证书文件

apk_key_map = GetApkCerts(input_zip)

GetApkCerts的实现如下,其实质是读取了targetfile中/META/apkcerts.txt文件

def GetApkCerts(tf_zip):

certmap = common.ReadApkCerts(tf_zip)

# apply the key remapping to the contents of the file

for apk, cert in certmap.iteritems():

certmap[apk] = OPTIONS.key_map.get(cert, cert)

# apply all the -e options, overriding anything in the file

for apk, cert in OPTIONS.extra_apks.iteritems():

if not cert:

cert = "PRESIGNED"

certmap[apk] = OPTIONS.key_map.get(cert, cert)

return certmap

def ReadApkCerts(tf_zip):

"""Given a target_files ZipFile, parse the META/apkcerts.txt file

and return a {package: cert} dict."""

certmap = {}

for line in tf_zip.read("META/apkcerts.txt").split("\n"):

line = line.strip()

if not line:

continue

m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'

r'private_key="(.*)"$', line)

if m:

name, cert, privkey = m.groups()

public_key_suffix_len = len(OPTIONS.public_key_suffix)

private_key_suffix_len = len(OPTIONS.private_key_suffix)

if cert in SPECIAL_CERT_STRINGS and not privkey:

certmap[name] = cert

elif (cert.endswith(OPTIONS.public_key_suffix) and

privkey.endswith(OPTIONS.private_key_suffix) and

cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):

certmap[name] = cert[:-public_key_suffix_len]

else:

raise ValueError("failed to parse line from apkcerts.txt:\n" + line)

return certmap

在这里可以分析下apkcerts文件,其内容格式如下:

name="RecoveryLocalizer.apk" certificate="build/target/product/security/testkey.x509.pem" private_key="build/target/product/security/testkey.pk8"

name="CtsVerifier.apk" certificate="build/target/product/security/testkey.x509.pem" private_key="build/target/product/security/testkey.pk8"

....

ame="CtsShimPrivUpgradePrebuilt.apk" certificate="PRESIGNED" private_key=""

name="CtsShimPrivUpgradeWrongSHAPrebuilt.apk" certificate="PRESIGNED" private_key=""

...

可以看出每一个apk中,都指定了证书的位置以及私钥文件路径,可以对比出,当应用的Android.mk中以platform签名,其格式为:

LOCAL_CERTIFICATE := platform

name="HdmiCts.apk" certificate="build/target/product/security/platform.x509.pem" private_key="build/target/product/security/platform.pk8"

一般apk以presigned签名的,则为:

LOCAL_CERTIFICATE := PRESIGNED

name="AllCast.apk" certificate="PRESIGNED" private_key=""

以media签名的,则为:

LOCAL_CERTIFICATE := media

name="Gallery.apk" certificate="build/target/product/security/media.x509.pem" private_key="build/target/product/security/media.pk8"

所以该文件定义了每个文件的证书以及签名情况,如果回到编译系统,可以看出该文件的编译规则:

APKCERTS_FILE := $(intermediates)/$(name).txt

$(APKCERTS_FILE):

@echo APK certs list: $@

@mkdir -p $(dir $@)

@rm -f $@

$(foreach p,$(PACKAGES),\

$(if $(PACKAGES.$(p).EXTERNAL_KEY),\

$(call _apkcerts_echo_with_newline,\

'name="$(p).apk" certificate="EXTERNAL" \

private_key=""' >> $@),\

$(call _apkcerts_echo_with_newline,\

'name="$(p).apk" certificate="$(PACKAGES.$(p).CERTIFICATE)" \

private_key="$(PACKAGES.$(p).PRIVATE_KEY)"' >> $@)))

# In case value of PACKAGES is empty.

$(hide) touch $@

.PHONY: apkcerts-list

apkcerts-list: $(APKCERTS_FILE)

可以看出编译伪目标apkcerts-list时,编译系统就会遍历$(PACKAGES),并将apk的信息记录在apkcerts.txt文档里。

在编译每一个Apk时,package_internal.mk会读取LOCAL_CERTIFICATE参数,并记录信息如下:

PACKAGES.$(LOCAL_PACKAGE_NAME).PRIVATE_KEY := $(private_key)

PACKAGES.$(LOCAL_PACKAGE_NAME).CERTIFICATE := $(certificate)

在编译targetfiles的时候,会编译该文件:

$(BUILT_TARGET_FILES_PACKAGE): \

$(INSTALLED_BOOTIMAGE_TARGET) \

$(INSTALLED_RADIOIMAGE_TARGET) \

$(INSTALLED_RECOVERYIMAGE_TARGET) \

$(INSTALLED_SYSTEMIMAGE) \

$(INSTALLED_USERDATAIMAGE_TARGET) \

$(INSTALLED_CACHEIMAGE_TARGET) \

$(INSTALLED_VENDORIMAGE_TARGET) \

$(INSTALLED_ANDROID_INFO_TXT_TARGET) \

$(SELINUX_FC) \

$(APKCERTS_FILE) \

$(HOST_OUT_EXECUTABLES)/fs_config \

| $(ACP)

5.处理targetfile中文件

对于targetfile中文件,核心部分调用如下方法:

ProcessTargetFiles(input_zip, output_zip, misc_info,

apk_key_map, key_passwords,

platform_api_level,

codename_to_api_level_map)

主要关心Apk部分:

for info in input_tf_zip.infolist():

if info.filename.startswith("IMAGES/"):

continue

...

# Sign APKs.

if info.filename.endswith(".apk"):

name = os.path.basename(info.filename)

key = apk_key_map[name]

if key not in common.SPECIAL_CERT_STRINGS:

print " signing: %-*s (%s)" % (maxsize, name, key)

signed_data = SignApk(data, key, key_passwords[key], platform_api_level,

codename_to_api_level_map)

common.ZipWriteStr(output_tf_zip, out_info, signed_data)

else:

# an APK we're not supposed to sign.

print "NOT signing: %s" % (name,)

common.ZipWriteStr(output_tf_zip, out_info, data)

...

只要apk的key不是以"PRESIGNED"或者"EXTERNAL"签名的,都会去重新签名:

SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")

3.2 更新system,recovery签名

当sign_target_files_apks指定了-O参数时,将会执行如下逻辑:

if OPTIONS.replace_ota_keys:

new_recovery_keys = ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)

if new_recovery_keys:

write_to_temp("RECOVERY/RAMDISK/res/keys", 0o755 << 16, new_recovery_keys)

即当制定了-O参数时,将会调用ReplaceOtaKeys方法。

获取/META/otakeys.txt

当方案中定义了PRODUCT_OTA_PUBLIC_KEYS时,在编译时会将内容写入otakeys.txt文件中

try:

keylist = input_tf_zip.read("META/otakeys.txt").split()

except KeyError:

raise common.ExternalError("can't read META/otakeys.txt from input")

2.获取recovery的证书

同理,如果在方案中制定了extra_recovery_keys,也会从misc_info中找证书

extra_recovry_keys = misc_info.get("extra_recovery_keys", None)

if extra_recovery_keys:

extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"

for k in extra_recovery_keys.split()]

if extra_recovery_keys:

print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys)

else:

extra_recovery_keys = []

3.对mapped_keys赋值

mapped_keys = []

for k in keylist:

m = re.match(r"^(.*)\.x509\.pem$", k)

if not m:

raise common.ExternalError(

"can't parse \"%s\" from META/otakeys.txt" % (k,))

k = m.group(1)

mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")

if mapped_keys:

print "using:\n ", "\n ".join(mapped_keys)

print "for OTA package verification"

else:

devkey = misc_info.get("default_system_dev_certificate",

"build/target/product/security/testkey")

mapped_keys.append(

OPTIONS.key_map.get(devkey, devkey) + ".x509.pem")

print "META/otakeys.txt has no keys; using", mapped_keys[0]

假如otakey文件中有内容,则将第一个key添加到mapped_keys中。否则就默认为系统的testkey。

4.利用dumpkey.jar为recovery创建新的key

p = common.Run(["java", "-jar",

os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")]

+ mapped_keys + extra_recovery_keys,

stdout=subprocess.PIPE)

new_recovery_keys, _ = p.communicate()

if p.returncode != 0:

raise common.ExternalError("failed to run dumpkeys")

common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys",

new_recovery_keys)

通过之前的extra_recovery_keys作为参数,将公钥内容打印出来,dumpkey.jar的内容如下:

1190000014662178?utm_source=channel-hottest

5.更新otacerts.zip

最后会将mapped_keys中的文件写入otacerts.zip中。如果otakey为空,则默认为testkey.x509.pem,如果指定了key的路径(-d),以及设置了-O,那么由于testkey通过方法BuildKeyMap绑定了releasekey,因此会替换为releasekey.x509.pem

temp_file = cStringIO.StringIO()

certs_zip = zipfile.ZipFile(temp_file, "w")

for k in mapped_keys:

common.ZipWrite(certs_zip, k)

common.ZipClose(certs_zip)

common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",

temp_file.getvalue())

4. ota_from_target_files.py

在上述步骤中,通过对targetfiles中的otacerts.zip以及recovery的/res/keys进行更新后,生成出来的固件假如与后续的ota包签名不符,那么在校验的时候也是会失败,所以在生成ota包时,也必须指定相应的公钥。

在ota_from_target_files中有如下的解析:

-k (--package_key) Key to use to sign the package (default is

the value of default_system_dev_certificate from the input

target-files's META/misc_info.txt, or

"build/target/product/security/testkey" if that value is not

specified).

并且在选项中假如未定义"--no_signing",而且-k未指定,将会使用原生的的testkey。

# Use the default key to sign the package if not specified with package_key.

if not OPTIONS.no_signing:

if OPTIONS.package_key is None:

OPTIONS.package_key = OPTIONS.info_dict.get(

"default_system_dev_certificate",

"build/target/product/security/testkey")

当OTA包完成所有的打包工作后,最终会调用到如下方法,表明要对整包进行签名。

# Sign the whole package to comply with the Android OTA package format.

def SignOutput(temp_zip_name, output_zip_name)

其实现如下:

def SignOutput(temp_zip_name, output_zip_name):

key_passwords = common.GetKeyPasswords([OPTIONS.package_key])

pw = key_passwords[OPTIONS.package_key]

common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,

whole_file=True)

common.GetKeyPasswords将会提示用户输入密码,假如密码与之前创建公钥私钥的密码不对,则提示错误,匹配才能往下进行,并返回更新后{key:password}

根据ota_from_targetfiles中指定的package_key,找到其密码pw

调用SignFile对OTA包进行整包签名

def SignFile(input_name, output_name, key, password, min_api_level=None,

codename_to_api_level_map=dict(),

whole_file=False):

"""Sign the input_name zip/jar/apk, producing output_name. Use the

given key and password (the latter may be None if the key does not

have a password.

If whole_file is true, use the "-w" option to SignApk to embed a

signature that covers the whole file in the archive comment of the

zip file.

min_api_level is the API Level (int) of the oldest platform this file may end

up on. If not specified for an APK, the API Level is obtained by interpreting

the minSdkVersion attribute of the APK's AndroidManifest.xml.

codename_to_api_level_map is needed to translate the codename which may be

encountered as the APK's minSdkVersion.

"""

java_library_path = os.path.join(

OPTIONS.search_path, OPTIONS.signapk_shared_library_path)

cmd = [OPTIONS.java_path, OPTIONS.java_args,

"-Djava.library.path=" + java_library_path,

"-jar",

os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]

cmd.extend(OPTIONS.extra_signapk_args)

if whole_file:

cmd.append("-w")

min_sdk_version = min_api_level

if min_sdk_version is None:

if not whole_file:

min_sdk_version = GetMinSdkVersionInt(

input_name, codename_to_api_level_map)

if min_sdk_version is not None:

cmd.extend(["--min-sdk-version", str(min_sdk_version)])

cmd.extend([key + OPTIONS.public_key_suffix,

key + OPTIONS.private_key_suffix,

input_name, output_name])

p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

if password is not None:

password += "\n"

p.communicate(password)

if p.returncode != 0:

raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))

这里实际进行的命令是:

java -Xmx2048m -Djava.library.path=$ANDROID_BUILD_TOP/out/host/linux-x86/lib64 -jar $ANDROID_BUILD_TOP/out/host/linux-x86/framework/signapk.jar -w $ANDROID_BUILD_TOP/build/target/product/security/testkey.x509.pem $ANDROID_BUILD_TOP/build/target/product/security/testkey.pk8 $1 $2

通过反编译可以看出对ota包的签名实际操作如下:

计算出key的个数,这里由于公钥私钥成对出现,而且指定输入输出文件,那么key的对数即为在-w之后的个数除以2,再减去1,ota签名时numKeys为1.

int numKeys = (args.length - argstart) / 2 - 1;

if ((signWholeFile) && (numKeys > 1)) {//这里证明了可以的对数只能为1

System.err.println("Only one key may be used with -w.");

System.exit(2);

}

载入输入文件和输出文件参数

String inputFilename = args[(args.length - 2)];

String outputFilename = args[(args.length - 1)];

inputJar = new JarFile(new File(inputFilename), false);

outputFile = new FileOutputStream(outputFilename);

读取公钥内容

File firstPublicKeyFile = new File(args[(argstart + 0)]);//获取公钥文件

X509Certificate[] publicKey = new X509Certificate[numKeys];//新建X509证书

try {

for (int i = 0; i < numKeys; i++) {

int argNum = argstart + i * 2;

publicKey[i] = readPublicKey(new File(args[argNum]));//将公钥读取到X509结构的publicKey中

hashes |= getDigestAlgorithm(publicKey[i], minSdkVersion);//计算摘要

}

} catch (IllegalArgumentException e) {

System.err.println(e);

System.exit(1);

}

读取私钥内容

timestamp -= TimeZone.getDefault().getOffset(timestamp);

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]));

}

进行签名

signWholeFile(inputJar, firstPublicKeyFile, publicKey[0], privateKey[0], timestamp, minSdkVersion, outputFile);

其实现如下:

CMSSigner cmsOut = new CMSSigner(inputJar, publicKeyFile, publicKey, privateKey, timestamp, minSdkVersion, outputStream);

ByteArrayOutputStream temp = new ByteArrayOutputStream();

byte[] message = "signed by SignApk".getBytes("UTF-8");

temp.write(message);//将message写入输出流

temp.write(0);

cmsOut.writeSignatureBlock(temp);

byte[] zipData = cmsOut.getSigner().getTail();

//检查zip格式核心目录结束标识是否为504B0506

if ((zipData[(zipData.length - 22)] != 80) || (zipData[(zipData.length - 21)] != 75) || (zipData[(zipData.length - 20)] != 5) || (zipData[(zipData.length - 19)] != 6))

{

throw new IllegalArgumentException("zip data already has an archive comment");

}

int total_size = temp.size() + 6;

//检查签名大小

if (total_size > 65535) {

throw new IllegalArgumentException("signature is too big for ZIP file comment");

}

//结尾格式以2字节`signature_staret` ff ff 2字节`total_size`结尾

int signature_start = total_size - message.length - 1;

temp.write(signature_start & 0xFF);

temp.write(signature_start >> 8 & 0xFF);

temp.write(255);

temp.write(255);

temp.write(total_size & 0xFF);

temp.write(total_size >> 8 & 0xFF);

temp.flush();

//检查temp流的结尾格式

byte[] b = temp.toByteArray();

for (int i = 0; i < b.length - 3; i++) {

if ((b[i] == 80) && (b[(i + 1)] == 75) && (b[(i + 2)] == 5) && (b[(i + 3)] == 6)) {

throw new IllegalArgumentException("found spurious EOCD header at " + i);

}

}

outputStream.write(total_size & 0xFF);

outputStream.write(total_size >> 8 & 0xFF);

temp.writeTo(outputStream);//将temp流写到输出文件中

5. 校验OTA包

5.1 RecoverySystem校验OTA包

RecoverySystem中有校验OTA包的接口

public static void verifyPackage(File packageFile,

ProgressListener listener,

File deviceCertsZipFile)

其校验流程如下:

1.获取OTA包

final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");

2.校验OTA包的尾部

raf.seek(fileLen - 6);

byte[] footer = new byte[6];

raf.readFully(footer);

//校验后6字节中间的两个字节是否为ff,与signapk.jar逻辑相同

if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {

throw new SignatureException("no signature in file (no footer)");

}

//获取commentSize以及signatureStart

final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);

final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);

byte[] eocd = new byte[commentSize + 22];

raf.seek(fileLen - (commentSize + 22));

raf.readFully(eocd);

// Check that we have found the start of the

// end-of-central-directory record.

//检查核心标识是否为504b0506

if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||

eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {

throw new SignatureException("no signature in file (bad footer)");

}

//检查eocd后四个字节是否是EOCD标识,如果是则报错

for (int i = 4; i < eocd.length-3; ++i) {

if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&

eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {

throw new SignatureException("EOCD marker found after start of EOCD");

}

}

3.从OT包中获取证书

// Parse the signature

PKCS7 block =

new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));

//获取证书

// Take the first certificate from the signature (packages

// should contain only one).

X509Certificate[] certificates = block.getCertificates();

if (certificates == null || certificates.length == 0) {

throw new SignatureException("signature contains no certificates");

}

X509Certificate cert = certificates[0];

//获取公钥

PublicKey signatureKey = cert.getPublicKey();

SignerInfo[] signerInfos = block.getSignerInfos();

if (signerInfos == null || signerInfos.length == 0) {

throw new SignatureException("signature contains no signedData");

}

SignerInfo signerInfo = signerInfos[0];

4.对比公钥信息

// Check that the public key of the certificate contained

// in the package equals one of our trusted public keys.

boolean verified = false;

HashSet trusted = getTrustedCerts(

deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);

for (X509Certificate c : trusted) {

if (c.getPublicKey().equals(signatureKey)) {

verified = true;

break;

}

}

if (!verified) {

throw new SignatureException("signature doesn't match any trusted key");

}

确保OTA包中获取的公钥是在信任的keylist之中,信任的keylist是从以下目录获取的:

private static final File DEFAULT_KEYSTORE =

new File("/system/etc/security/otacerts.zip");

这个文件就是在sign_target_files_apks.py中通过指定-O选项时更新的。假如没有指定新的签名目录,那么使用原生的testkey作为密钥。所以从校验可以看出,至少对ota整包的签名的公钥信息必须要与待OTA升级的system/etc/otacerts.zip中的公钥信息是要一致的,否则将在校验时出错。

这个校验流程与常规的签名校验一致,假如加密文件,这里OTA包,使用了私钥进行签名,并在尾部附上公钥,那么正常而言,使用该公钥即可对其进行验证,但是这里就多了一个流程是公钥必须要和需要升级的固件是一致的,相当于CA的一个作用证明该公钥是有效的,才会继续使用公钥去计算出OTA包中的摘要,然后通过该摘要值与给到的OTA包进行计算的摘要值进行对比,保证该OTA包是没有经过修改的。

5.对比摘要值

SignerInfo verifyResult = block.verify(signerInfo, new InputStream

这里的veriry中还实现了一个read方法,应当是对ota包中的内容进行读取,并与signerinfo的信息进行比较,其中read的内容范围为:

// The signature covers all of the OTA package except the

// archive comment and its 2-byte length.

long toRead = fileLen - commentSize - 2;

当校验结果为null时出错

if (verifyResult == null) {

throw new SignatureException("signature digest verification failed");

}

5.2 recovery校验OTA包

recovery的校验流程与RecoverySystem中相仿。

6. 结论

一般而言,正常编译OTA包与编译固件,均是默认使用testkey进行签名

假如指定了新的签名目录,那么必须保证,待升级的固件中的公钥信息(包括system以及recovery)都要与升级的OTA包中的公钥信息等匹配,即使用同一对密钥对。

android ota 版本校验,OTA升级签名校验简析相关推荐

  1. Android V1及V2签名原理简析

    Android为了保证系统及应用的安全性,在安装APK的时候需要校验包的完整性,同时,对于覆盖安装的场景还要校验新旧是否匹配,这两者都是通过Android签名机制来进行保证的,本文就简单看下Andro ...

  2. Android应用安全与防范之签名校验

    2019独角兽企业重金招聘Python工程师标准>>> Android黑产品里面有一个叫做二次打包,也称为重打包.通过反编译应用后,可以得到smali源码,往其中注入代码或者修改相应 ...

  3. android so 签名校验,Android-NDK-之so文件签名校验

    前面说了so文件可以大大减少数据被泄露的情况,但这得是有前提条件. 因为正常的so文件,别人是可以拿到后可以直接在项目中使用的. 那有什么方式可以增加难度,让别人需要一定复杂操作才能使用该so文件库呢 ...

  4. 展讯android智能机平台FDL1,FDL2,SPL文件下载问题简析

    首先,我们要了解这样一个背景知识:展讯的每颗智能芯片(其他智能机平台应该也是如此)内部都有IROM和IRAM,IROM里有固化的Romcode(用于与PC端工具通讯,下载程序). 但是...... 但 ...

  5. android7.0及以上版本签名校验过程详解

    对于新的签名方案APK Signature Scheme v2,在这篇文章中已经有详细的介绍http://www.tuicool.com/articles/bURRVrj.从这篇文章中可以知道,新的签 ...

  6. autojs用签名校验保护app

    牙叔教程 简单易懂 测试环境 Autojs版本: 9.0.4 Android版本: 8.0.0 Android Studio版本: 4.1.2 签名概念 目的: 为了确认某个信息确实是由某个发送方发送 ...

  7. 蓝牙BLE OTA 升级 CRC校验

    CRC 循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能 ...

  8. Android OTA版本任意升级

    Android目前的版本只能旧版本升级新版本,无法升级旧版本. Android OTA升级主要是ota_from_target_files.py这个脚本,文件目录在build/tools/releas ...

  9. Android A/B System OTA分析(四)系统的启动和升级

    Android从7.0开始引入新的OTA升级方式,A/B System Updates,这里将其叫做A/B系统,涉及的内容较多,分多篇对A/B系统的各个方面进行分析.本文为第四篇,系统的启动和升级. ...

最新文章

  1. python四大软件-PYPL 9月编程语言排行榜发布 Python一枝独秀
  2. 统一代码段与非一致代码段
  3. 服务器点对点直连,点对点网络连接怎么建立有什么作用
  4. AgileConfig 1.6.0 发布 - 支持服务注册与发现
  5. 包装设计中文字字体的logo设计要注意什么
  6. BAT研发Java面试36题总结:Spring+Redis+Docker+Dubbo
  7. ios html gif 显示,显示gif时出现巨大的内存使用Swift iOS
  8. django model中的DateField()转为时间戳
  9. 边缘设备上的实时AI人员检测:以实时模式检测视频中的人员
  10. Amesim液压仿真基础与液压知识经验结合专题
  11. 现代雷达系统分析与设计---数字中频正交采样
  12. 把java代码导成pdf_Java将Excel导出成pdf文件
  13. css2仿微信导航栏-滑动门
  14. 摆脱垃圾服务商 选择微空间免费空间
  15. 2021年5月11日19:50:56 学习 真的很难吗?
  16. 树莓派Raspberry Pi上安装和使用RPi.GPIO模块以及引脚对照表
  17. 小程序引入第三方字体
  18. 《资治通鉴》—— 三家分晋
  19. BD-Rate和BD-PSNR
  20. java回车监听_java学习:给登入界面添加键盘监听,按回车键登入

热门文章

  1. 使用自己的按钮关闭或刷新RadWindow
  2. Vim的一些使用技巧
  3. Itext导出pdf文件
  4. Centos 7 修改内核启动顺序
  5. Double 与 Float 的值的比較结果
  6. 【css】怎么让Chrome支持小于12px 的文字
  7. HTML5拖放(drag and drop)与plupload的懒人上传
  8. 解决安装linux时未挂载文件而导致的重启失败
  9. [转]代码分析工具FxCop1.36之一:介绍与使用
  10. PostgreSQL字符集问题