Adroid 签名机制V1,V2,V3

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Adroid 签名机制V1,V2,V3相关的知识,希望对你有一定的参考价值。

参考技术A

apk解压后META-INF 文件夹下有三个文件:MANIFEST.MF、CERT.SF、CERT.RSA。

该文件中保存的内容其实就是逐一遍历 APK 中的所有条目

这里会把之前生成的 CERT.SF 文件,用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。这里要注意的是,android APK 中的 CERT.RSA 证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的,用户可以在本地机器自行生成这个自签名证书。Android 目前不对应用证书进行 CA 认证。

RSA 文件加密了,所以我们需要用 openssl 命令才能查看其内容

签名验证是发生在 APK 的安装过程中,一共分为三步:

为了解决V1的缺点,在 Android 7.0 Nougat 中引入了全新的 APK Signature Scheme v2。

APK 签名方案 v2 是一种全文件签名方案,该方案能够发现对 APK 的受保护部分进行的所有更改,从而有助于加快验证速度并增强完整性保证。

由于在 v1 仅针对单个 ZIP 条目进行验证,因此,在 APK 签署后可进行许多修改 — 可以移动甚至重新压缩文件。事实上,编译过程中要用到的 ZIPalign 工具就是这么做的,它用于根据正确的字节限制调整 ZIP 条目,以改进运行时性能。而且我们也可以利用这个东西,在打包之后修改 META-INF 目录下面的内容,或者修改 ZIP 的注释来实现多渠道的打包,在 v1 签名中都可以校验通过。

v2 签名将验证归档中的所有字节,而不是单个 ZIP 条目,因此,在签署后无法再运行 ZIPalign(必须在签名之前执行)。正因如此,现在,在编译过程中,Google 将压缩、调整和签署合并成一步完成。

简单来说,v2 签名模式在原先 APK 块中增加了一个新的块(签名块),新的块存储了签名,摘要,签名算法,证书链,额外属性等信息,这个块有特定的格式,具体格式分析见后文,先看下现在 APK 成什么样子了。

为了保护 APK 内容,整个 APK(ZIP 文件格式)被分为以下 4 个区块:

其中,应用签名方案的签名信息会被保存在 区块 2(APK Signing Block)中,而区块 1(Contents of ZIP entries)、区块 3(ZIP Central Directory)、区块 4(ZIP End of Central Directory)是受保护的,在签名后任何对区块 1、3、4 的修改都逃不过新的应用签名方案的检查。

其中 v2 签名机制是在 Android 7.0 以及以上版本才支持。因此对于 Android 7.0 以及以上版本,在安装过程中,如果发现有 v2 签名块,则必须走 v2 签名机制,不能绕过。否则降级走 v1 签名机制。
v1 和 v2 签名机制是可以同时存在的,其中对于 v1 和 v2 版本同时存在的时候,v1 版本的 META_INF 的 .SF 文件属性当中有一个 X-Android-APK-Signed 属性,因此如果想绕过 v2 走 v1 校验是不行的。

之前的渠道包生成方案是通过在 META-INF 目录下添加空文件,用空文件的名称来作为渠道的唯一标识。但在新的应用签名方案下 META-INF 已经被列入了保护区了,向 META-INF 添加空文件的方案会对区块 1、3、4 都会有影响。

可以参考: 美团解决方案 。

Android 9.0 中引入了新的签名方式,它的格式大体和 v2 类似,在 v2 插入的签名块(Apk Signature Block v2)中,又添加了一个新快(Attr块)。

在这个新块中,会记录我们之前的签名信息以及新的签名信息,以密钥转轮的方案,来做签名的替换和升级。这意味着,只要旧签名证书在手,我们就可以通过它在新的 APK 文件中,更改签名。

v3 签名新增的新块(attr)存储了所有的签名信息,由更小的 Level 块,以链表的形式存储。

其中每个节点都包含用于为之前版本的应用签名的签名证书,最旧的签名证书对应根节点,系统会让每个节点中的证书为列表中下一个证书签名,从而为每个新密钥提供证据来证明它应该像旧密钥一样可信。

这个过程有点类似 CA 证书的证明过程,已安装的 App 的旧签名,确保覆盖安装的 APK 的新签名正确,将信任传递下去。

需要注意的是,对于覆盖安装的情况,签名校验只支持升级,而不支持降级。也就是说设备上安装了一个使用 v1 签名的 APK,可以使用 v2 签名的 APK 进行覆盖安装,反之则不允许。

如何从交易收据重新创建原始交易以验证 v,r,s 签名?

【中文标题】如何从交易收据重新创建原始交易以验证 v,r,s 签名?【英文标题】:How can I re create raw transaction from the transaction receipt to verify v,r,s signature? 【发布时间】:2018-09-23 04:00:58 【问题描述】:

我正在尝试验证以太坊交易。

这是我的步骤。 1. 进行交易 2. 使用 eth.getTransaction() 获取交易 3. 使用 ethereumjs-tx 重新创建交易

但有时我无法验证交易。

案例一:私测网简单发送以太币交易

    获取交易详情

    块哈希:“0x2125539ac67b4569828737ffb1731048e00121954f0555d0dc96af665071a62b”, 区块编号:24615, 来自:“0x81c24515cdf1a4b68f34e3e2824d44b28d00f010”, 气体:90000, gasPrice: 18000000000, 哈希:“0x9e4ce952759eae925173c6c6055c1afe577a48462caacd8d4fb742e911eae053”, 输入:“0x”, 随机数:0, r: "0x826b5348acbec72bab39c5debc8493e34d23b351bc7c20ded25d2a4eed736093", s: "0x2a87e18b22c76d61ce9d6a4d56949afa025f1611aa6bb9fd9d6c502d61f7361b", 至:“0x487f5eea74ea5f3e94093d8b0501f1d2b0d5310a”, 交易指数:0, v: "0x10f469", 值:1000000000000000000

    然后使用交易详细信息和 ethereumjs-tx 创建一个交易。

    const EthereumTx = require('ethereumjs-tx') 常量 test_arr1 = 声明:“0x”+parseInt(0, 10).toString(16), gasPrice: "0x"+parseInt(18000000000, 10).toString(16), gasLimit: "0x"+parseInt(90000, 10).toString(16), 至:'0x487f5eea74ea5f3e94093d8b0501f1d2b0d5310a', 值:“0x”+parseInt(1000000000000000000, 10).toString(16), 数据:'0x', v: '0x10f469', r: '0x826b5348acbec72bab39c5debc8493e34d23b351bc7c20ded25d2a4eed736093', s: '0x2a87e18b22c76d61ce9d6a4d56949afa025f1611aa6bb9fd9d6c502d61f7361b' const tx = new EthereumTx(test_arr1); 常量恢复地址 = "0x"+tx.getSenderAddress().toString('hex') 恢复地址是 0x81c24515cdf1a4b68f34e3e2824d44b28d00f010 这是正确的

案例 2:ropsten 测试网中的智能合约

    获取交易详情

    块哈希:“0xead9335751dbdb4a874b2bb48ac15ddafbec6f2ba55a5932bf6ec1a0475166e7”, 区块编号:3026266, 来自:“0x0d6883a0e7071513c7d90a27bf2715bc71ecf107”, 气:309588, gasPrice: 18000000000, 哈希:“0xe69d8b108af59198857dd5b045769748dbe1ca3ad9bba7dbbb512643b9d85b5a”, 输入:“0x03e63bdb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000890000000b000000012e507fe5cce2f11c2a520a265f593c08372834fec925f84bbe5a72793ec5096d03fd11970afed8b767adfed60caf3f0c1de0dbda06d48f9afc3661717dbf85641b3f011114d3a41bf16a8d8cc33769aba2abe14efb14487295c80da13b3e333707202d1bdea56f75616202491b4bcc437b6a5b7a79284a08e28bcd0a90e3d87bf10000000000000000000000000000000000000000000000” 随机数:129, r: "0xdd4fe550275bd35ffd4babf6ac3578575594011f027923046da78a7b179ffb66", s: "0x2584e1f3f36185f6cd9358146f2479dde41dbb85ced5859c845a065cb5bdc42b", 至:“0xad5e2d5cb93f098423597c891d2f1ed35f904ca1”, 交易指数:0, v:“0x2a”, 值:0

    然后使用交易详细信息和 ethereumjs-tx 创建一个交易。

    const EthereumTx = require('ethereumjs-tx') 常量 test_arr2 = 宣布:“0x”+parseInt(129, 10).toString(16), gasPrice: "0x"+parseInt(18000000000, 10).toString(16), gasLimit: "0x"+parseInt(309588, 10).toString(16), 至:'0xad5e2d5cb93f098423597c891d2f1ed35f904ca1', 值:“0x”, 数据: '0x03e63bdb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000890000000b000000012e507fe5cce2f11c2a520a265f593c08372834fec925f84bbe5a72793ec5096d03fd11970afed8b767adfed60caf3f0c1de0dbda06d48f9afc3661717dbf85641b3f011114d3a41bf16a8d8cc33769aba2abe14efb14487295c80da13b3e333707202d1bdea56f75616202491b4bcc437b6a5b7a79284a08e28bcd0a90e3d87bf10000000000000000000000000000000000000000000000', v: '0x2a', r: '0xdd4fe550275bd35ffd4babf6ac3578575594011f027923046da78a7b179ffb66', s: '0x2584e1f3f36185f6cd9358146f2479dde41dbb85ced5859c845a065cb5bdc42b', 链号:3 const tx2 = new EthereumTx(test_arr2); 常量恢复地址 = "0x"+tx2.getSenderAddress().toString('hex') 恢复地址是 0x9c9d4315824275f545b2e96026a7075f75125b9b 这是不正确的。应该是 0x0d6883a0e7071513c7d90a27bf2715bc71ecf107

这是为什么呢? 如何正确重新创建原始交易?

或者有没有其他方法可以用 v,r,s 签名验证交易?

提前致谢。

【问题讨论】:

【参考方案1】:

如果您使用的是 web3js v1.0,您可以简单地使用web3.eth.accounts.recover。他们文档中的示例:

web3.eth.accounts.recover(
    messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655',
    v: '0x1c',
    r: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd',
    s: '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029'
)
> "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23"

另一种选择是您可以在使用 Solidity 的 ecrecover() 的合约中调用 view 函数。来自Solidity docs:

ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) 返回(地址):从椭圆曲线签名中恢复与公钥关联的地址,出错返回零

例子:

function verify(bytes prefix, bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) external pure returns(address) 
  bytes32 prefixedHash = keccak256(prefix, msgHash);
  return ecrecover(prefixedHash, v, r, s);

你必须小心前缀。不同的客户端可能使用不同的前缀。例如,geth 使用prefix = "\x19Ethereum Signed Message:\n32" 签署交易。

【讨论】:

感谢您回复我。在这种情况下,msgHash 是什么?似乎 hash 和 blockHash 不是一个...... 这是您签名的消息的 sha3 哈希值。顺便说一句,你原来的方法也没有错。我运行了你签名的 tx 并得到了正确的地址: > signed.getSenderAddress().toString('hex'); '0d6883a0e7071513c7d90a27bf2715bc71ecf107'。可能是因为您在对象中拼错了“nonce”? 是的,你是对的!这是因为我的错字。谢谢!

以上是关于Adroid 签名机制V1,V2,V3的主要内容,如果未能解决你的问题,请参考以下文章

Android 他山之石,可以攻玉!一篇文章看懂 v1/v2/v3 签名机制

V1、V2、V3签名

Android每日一题:v3签名key和v2还有v1有啥区别

Android基础『V1V2V3签名』

APK签名机制之——V2签名机制详解

Android V1及V2签名原理简析