前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >rk3399-android9.0-secureboot介绍

rk3399-android9.0-secureboot介绍

作者头像
菜菜cc
发布2022-11-15 21:31:02
2.4K0
发布2022-11-15 21:31:02
举报
文章被收录于专栏:菜菜的技术博客

RK完整的Secureboot包括两部分,第一部分为Linux的Secureboot,第二部分为Android特有的AVB(Android Verified Boot)。开启了Secureboot的设备,会在启动时逐级校验各分区,一旦某一级校验不通过,则设备就无法启动。

Secureboot分为安全性校验与完整性校验。

  • 安全性校验: 为公钥的校验,借助于芯片的一次性可编程安全存储模块(OTP 或 efuse), 在rk3399上称为efuse。该检验流程为从efuse中读取公钥 hash,与计算的公钥 hash 先做对比,如果相同,则再将公钥用于解密固件 hash。
  • 完整性校验: 为校验固件的完整性,计算固件的 hash 与用公钥解密出来的 hash 对比是否一致。

AVB阶段安全性校验和完整性校验需要依赖于vbmeta.img,相关的公钥及描述信息存储在vbmeta.img中。

Secureboot流程

Secureboot涉及到的两级:maskrom —> miniloader、miniloader —> uboot、uboot—> kernel,但在Android上Secureboot部分只实现前两级,uboot—> kernel以及之后的启动校验交由AVB进行处理。以下以maskrom —> miniloader为例讲解Secureboot流程。

pc加密过程

(adsbygoogle=window.adsbygoogle||[]).push({})

使用rk提供的签名工具(rk_sign_tool)进行签名步骤及原理如下

1.该工具首先会产生一对密钥对,即:public key和privete key

2.使用SHA256计算镜像的hash,并使用privete key对镜像的hash进行RSA2048签名

3.使用SHA256计算出public key的hash

4.将镜像+第2步中签名+public key进行打包形成新的镜像

5.第3步中的hash将会烧写到efuse中

设备解密过程

1.首先从新的镜像中获取public key计算hash值

2.从efuse中读取public key的hash值进行对比,如果相同则继续,否则启动失败

3.从镜像中获取签名,然后使用RSA2048计算hash

4.使用SHA256计算镜像的hash值,与第三步计算出来的hash进行对比,相同则继续,否则启动失败

AVB流程

AVB的核心结构为vbmeta,vbmeta分区存储了boot分区的hash,而对于systemvender分区,哈希树紧随在各自的分区数据之后,vbmeta分区只保存哈希树描述符中哈希树的根哈希(root hash),盐(salt)和偏移量(offset)。

uboot启动后,首先需要进行vbmeta的合法性验证,即安全性校验,RK的做法是将验证vbmeta的公钥信息经过trust加密后存储在security分区,其中trust分区的安全性又是受efuse验证的Secureboot进行保证的。uboot启动kernel前先验签vbmeta,vbmeta可信后,再取出vbmeta中的相关信息来进行其他分区的校验。

Merkle Tree

hash list

AVB在验证system分区时采用了动态校验的方式进行完整性校验,所以采用了分块进行hash的方式来校验。那么如何存储该数据块的hash,直接采用最暴力的方式,自然而然想到的是使用一个hash列表来存储。但是使用Hash列表来保证数据块的正确性还不够,黑客修改数据的同时,如果将Hash列表也对应修改了,这就无法保证数据块的正确性了。所以需要引入一个顶层的hash,将hash列表里的每个hash字符串拼在一起后再做一次hash运算,最后的hash值称之为root hash,只要保证该root hash的正确性即可。

但是AVB并未采用该简单结构。假设system的大小为1GB,数据块大小为4KB,则有26万个数据块,对应着hash列表就有26万个元素。AVB进行运行时校验,设备运行时读到哪个块就会对哪个块校验,将需要校验的块进行hash后更新具有26万个元素的hash列表中的一个元素后计算root hash,再与vbmeta中root hash作对比来判断数据是否正确。这个效率可想而知非常糟糕,所以AVB采用了一种称为Merkle Tree的树结构。

hash tree

Merkle Tree,通常也被称作Hash Tree,其叶子节点是数据块或者文件的hash值。非叶节点是其对应子节点串联字符串的hash。Hash 列表可以看作一种特殊的Merkle Tree,即树高为2的多叉Merkle Tree。

建树过程:

在树的最底层,和hash列表一样,将数据分成若干个小的数据块,有相应的hash与之对应。但是往上走,并不是直接去计算root hash,而是把相邻的两个hash合并成一个字符串,然后计算这个字符串的hash,将这个hash值作为两个节点的父节点。按照同样的方式,可以得到数目更少的新一级hash,最终必然形成一棵树,树的根节点即为root hash。

Merkle Tree的结构非常易于同步大文件或文件集合,按照查找树的查找思路,从root hash开始比对,依次往下查找到叶子节点即能找到需要重新同步或下载的数据块,其时间复杂度为O(logN),如果采用hash列表的方式,需要完整进行一遍遍历才能定位到不同的数据块,其时间复杂度为O(N)。Merkle Tree在数字签名、P2P网络、区块链等技术都有应用。回到本文介绍的AVB,AVB在运行时校验某一块时只需要更新Merkle Tree的一个分支即可计算出hash root,其运算时间比hash列表大大减少。在Android9上使用avbtool的python代码进行hash tree的生成,该算法跟上文描述略有不同,当1G的system进行4KB大小的划分,其生成的hash tree只有四层(包括root hash这一层),所以运行时计算hash只要沿着这个四层树的分支计算即可,可想而知效率大大提升。

avbtool中建树源码分析

以下分析一下Android9上hash tree的生成过程,涉及到用Python实现的avbtool源码的两个函数:calc_hash_level_offsetsgenerate_hash_tree

calc_hash_level_offsets

代码语言:javascript
复制
def calc_hash_level_offsets(image_size, block_size, digest_size):
  """Calculate the offsets of all the hash-levels in a Merkle-tree.

  Arguments:
    image_size: The size of the image to calculate a Merkle-tree for.
    block_size: The block size, e.g. 4096.
    digest_size: The size of each hash, e.g. 32 for SHA-256.

  Returns:
    A tuple where the first argument is an array of offsets and the
    second is size of the tree, in bytes.
  """
  level_offsets = []  # 用来存储每一层在bytearray中的偏移
  level_sizes = []    # 每一层占用的大小
  tree_size = 0       # 树的大小

  num_levels = 0      # 树的层数
  # size用于计算时表示当前层的下一层的数据大小,从第0层(计算数据块hash)开始,
  # 所以初始值为image的大小
  size = image_size   
  while size > block_size:
    # 计算当前层数据需要多少个块
    num_blocks = (size + block_size - 1) / block_size
    # round_to_multiple函数用来将第一个参数舍入到最接近第二个参数的倍数
    # 在这里就是对齐到block_size的整数倍
    # 计算当前层的hash digest需要占用的大小
    level_size = round_to_multiple(num_blocks * digest_size, block_size)

    level_sizes.append(level_size)
    tree_size += level_size
    num_levels += 1

    # 循环往上计算,所以更新size为当前层,用于计算上一层
    size = level_size

  # 计算每一层在bytearray中的偏移  
  for n in range(0, num_levels):
    offset = 0
    for m in range(n + 1, num_levels):
      offset += level_sizes[m]
    level_offsets.append(offset)

  return level_offsets, tree_size

Android9上将hash tree存储在bytearray中,所以需要事先计算好树的每一层在bytearray中的偏移,以及整个树需要多长的bytearray存储。注意,hash tree的建树过程上自下往上的。其实从calc_hash_level_offsets函数就可大致看出Android上hash tree的存储形态了,但更为形象的存储结构还是需要看generate_hash_tree函数。

generate_hash_tree

代码语言:javascript
复制
def generate_hash_tree(image, image_size, block_size, hash_alg_name, salt,
                       digest_padding, hash_level_offsets, tree_size):
  """Generates a Merkle-tree for a file.

  Args:
    image: The image, as a file.
    image_size: The size of the image.
    block_size: The block size, e.g. 4096.
    hash_alg_name: The hash algorithm, e.g. 'sha256' or 'sha1'.
    salt: The salt to use.
    digest_padding: The padding for each digest.
    hash_level_offsets: The offsets from calc_hash_level_offsets().
    tree_size: The size of the tree, in number of bytes.

  Returns:
    A tuple where the first element is the top-level hash and the
    second element is the hash-tree.
  """
  hash_ret = bytearray(tree_size)
  hash_src_offset = 0
  hash_src_size = image_size
  level_num = 0
  while hash_src_size > block_size:
    level_output = ''
    remaining = hash_src_size
    while remaining > 0:
      hasher = hashlib.new(name=hash_alg_name, string=salt)
      # Only read from the file for the first level - for subsequent
      # levels, access the array we're building.
      # 第0层直接按照block_size读取image来进行hash
      if level_num == 0:
        image.seek(hash_src_offset + hash_src_size - remaining)
        data = image.read(min(remaining, block_size))
      # 第0层之上的每一层都由取其下一层来进行hash,eg: 将第m-1层的数据分块hash后生成m层数据
      else:
        offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining
        # 以block_size为单位进行分块
        data = hash_ret[offset:offset + block_size]
      hasher.update(data)

      remaining -= len(data)
      if len(data) < block_size:
        hasher.update('\0' * (block_size - len(data)))
      level_output += hasher.digest()
      if digest_padding > 0:
        level_output += '\0' * digest_padding

    padding_needed = (round_to_multiple(
        len(level_output), block_size) - len(level_output))
    level_output += '\0' * padding_needed

    # Copy level-output into resulting tree.
    offset = hash_level_offsets[level_num]
    hash_ret[offset:offset + len(level_output)] = level_output

    # Continue on to the next level.
    hash_src_size = len(level_output)
    level_num += 1

  # 建树完成后,单独计算root hash
  hasher = hashlib.new(name=hash_alg_name, string=salt)
  hasher.update(level_output)
  return hasher.digest(), hash_ret

通过calc_hash_level_offsets函数计算好偏移和大小后,即可将参数传递给generate_hash_tree函数来建树了。 从建树代码的循环过程可以看出,该树的实现是将生成的hash拼接在一起作为这一层的数据,然后分块进行hash后再拼接在一起给到父层,而不是之前的描述Merkle Tree的两两子节点合并后计算hash作为父节点。

本文作者: Ifan Tsai  (菜菜)

本文链接: https://cloud.tencent.com/developer/article/2164593

版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Secureboot流程
    • pc加密过程
      • 设备解密过程
      • AVB流程
      • Merkle Tree
        • hash list
          • hash tree
            • avbtool中建树源码分析
            相关产品与服务
            对象存储
            对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档