虚拟化加密磁盘密钥设置方案浅析
- 前言
- 元数据分析
- 元数据格式
- 整体格式
- 头部格式
- 加密算法
- 密码校验
- key slot格式
- 其它字段
- 流程验证
前言
- 我们在虚拟化加密磁盘密钥设置方案浅析 — TKS1中介绍了加密磁盘密钥设置方案,TKS1对密钥设置(Linux Unified Key Setup)的流程和方案做了统一的定义,Linux将LUKS应用于磁盘加密技术,定义了遵循LUKS1 On-Disk Format Specification的加密磁盘头部。虚拟化组件QEMU的加密磁盘在实现上同样遵循上述规范。下面我们具体介绍其具体内容。
元数据分析
- LUKS1加密磁盘格式是基于TKS1的POC,因此LUKS1 On-Disk Format Specification的目的就是将TKS1的理论规范化并定义加密磁盘头部用于实现该规范。
- 在分析LUKS1标准的加密磁盘头部之前,我们先回顾TKS1中要解决的几个问题并对应列出其对应方案以及方案涉及到的具体输入、输出和参数信息,进而推断出LUKS1加密磁盘头部至少应该具备哪些元数据信息:
- 防止用户弱熵密码被暴力破解和字典攻击
TKS1通过PBKDF2函数来处理用户弱熵密码,派生出强熵密钥cipher作为master key的加密密钥。
PBKDF2涉及的输入有,password,salt,iterration-count,derived-key-length,输出cipher。
该方案涉及的关键元素password为用户输入或者第三方密钥管理服务提供,无需保存到磁盘,salt,iteration-count,derived-key-length为用户配置,需要作为元数据保存到磁盘。 - 当用户密码作为磁盘加密密钥,用户密码的泄露会导致加密磁盘数据的泄露
TKS1引入master key作为磁盘加密的密钥,设计两级加密方案将用户密码和加密磁盘的密钥解耦,可以避免当用户密码泄露但master key未泄露时,对已加密磁盘的重新加密。
该方案中涉及关键元素有master key,master key生命周期为,磁盘首次加密时由加密软件随机生成master key的明文,首次加密完成后明文被cipher加密变为密文,作为元数据保存到磁盘,磁盘解密时将master key密文从磁盘中取出再通过cipher解密得到磁盘的明文。总结,master key的在初始化时被随机生成,写磁盘完成后作为元数据被保存到磁盘,读磁盘操作前从磁盘元数据中读出。master key的密文需要作为元数据保存到磁盘。 - 最大程度防止密钥的非可靠删除导致的信息泄露
TKS1引入AF-Splitter算法将master key切分存储,LUKS在实现时AF-Splitter算法时将扩散因子H也设置为PBKDF2,因此masterr key在切分与合并时也会用到PBKDF2涉及的参数。LUKS实现AF-Splitter函数的伪代码如下:
可以看到其本身涉及到两个参数,一是切分的数据长度length,而是切分的组数(或者称为条带数)stripes,切分数据的长度就是master key的长度,而条带数允许用户配置。
- 总结上面分析,我们可以知道LUKS1作为TKS1的POC,至少要在磁盘头部存放以下元数据:PBKDF2的迭代次数,盐,master key长度,master key,AF-Splitter条带数。
除此之外,LUKS1作为标准规范,还会将标准参数化、兼容性及其它方面考虑在内,因此还会引入其它字段作为头部元数据。
元数据格式
整体格式
- LUKS加密磁盘整体格式如上图所示,分为三个主要部分,分别是:
- LUKS phdr: 磁盘头部,保存实现规范涉及的元数据
- Key Material: 存放key master反取证拆分再加密后得到的数据,LUKS可以支持多个用户密码,每个密码可以唯一解密一个key master,从上图我们看到有8个key slot,对应地可以存放8个KM,因此LUKS支持用户最多设置8个密码。
- bulk data: 磁盘密文数据
头部格式
- LUKS加密磁盘头部格式如下图所示:
加密算法
- LUKS支持用户指定加密磁盘的算法和模式,磁盘数据加密的伪代码如下:
- 其中加密算法由cipher-name指定指定,算法模式有cipher-mode指定,而key则是我们的master key。因此头部中也设计了cipher-name和cipher-mode两个字段用于存放用户设置的加密方式。
其中可用的加密算法cipher-name有:
可用的加密模式cipher-mode有:
密码校验
- 从LUKS加密磁盘头部格式中我们似乎看到前文分析的几个需要落盘的元数据信息,key-bites,mk-digest-salt,mk-digest-iter,但这里其实只有第1个如我们分析,key-bites代表master key的长度,而mk-digest-salt,mk-digest-iter并非用作用户密码派生时的PBKDF2输入参数,而是校验master key的时候用。这里一并介绍LUKS怎么校验用户输入密码的正确性,其步骤如下:
- 加密磁盘初始化时随机生成key-bites长度的master key
- 之后对master key也做一次PBKDF2的派生计算,得到master key的摘要信息mk-digest,派生计算的伪代码如下:
- 将以上信息mk-digest-salt, mk-digest-iteration-count, mk-digest作为元数据存放到LUKS加密磁盘头部
- 校验用户输入密码password时,可能是错误的,将password按照正常流程做PBKDF2做派生得到derived-key,然后逐一解密每个key slot得到master key的明文,将master key明文再按照b步骤做1次PBKDF2派生得到摘要信息并对比磁盘上读取的摘要信息,如果两者相同,说明用户输入的密码正确,如果不同说明用户密码不能解密当前key slot,之后遍历所有key slot,如果都没有匹配成功,说明用户密码不正确。
- 从上面的描述来看,我们大概明白mk-digest-salt, mk-digest-iteration-count, mk-digest这几个字段的设计目的:支持校验用户输入的密码。因为LUKS不能存储master key的明文在元数据头部,所以设计了摘要字段用于校验master key的明文,而LUKS生成摘要的方式也是PBKDF2,因此这个字段才引起我们的误解。那么用于派生用户密码的PBKDF2参数存放在哪里的呢?其实它存放在每个key slot中了,下面继续介绍key slot格式。
key slot格式
- 在LUKS头部的末尾,我们看到了8个key slot字段,这个字段才是我们前文提到的存放派生用户密码的PBKDF2参数的字段。对于每个key slot,用户都可单独指定PBKDF2的参数,因此PBKDF2在处理解密每个KM时其计算量也可能会不一样,有些密码可能迭代次数少,有些密码可能次数多,通过这样的布局,LUKS可以让每个用户密码互相独立不受影响。
- key slot的具体布局如下图所示:
- 可以看到除了迭代次数和盐值以外,还有1个参数和key master相关,即条带数,这是AF-Splitter算法的参数。其它的,active标识对应的KM是否使能,key-masterial-offset存放key slot关联的KM在磁盘的偏移地址。
其它字段
- 我们再回头逐一查看LUKS头部的其它元数据字段
- magic,6字节,标识磁盘为LUKS的魔数,其值为{‘L’,’U’,’K’,’S”,0xBA,0XBE}
- version,2字节,LUKS版本号
- hash-spec,PBKDF2算法除了上面介绍的参数以外,还有1个元素可以参数化,就是用于迭代计算的hash算法,LUKS也将其标准化,其可选的值为:
- payload-offset: 加密数据的起始偏移
- uuid: 磁盘uuid
流程验证
- TODO