前言
- 我们在虚拟化加密磁盘密钥设置方案浅析 — 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: 磁盘头部,保存实现规范涉及的元数据,是实现LUKS规范加密磁盘的重要字段。
- Key Material: 存放key master反取证拆分再加密后得到的数据,即虚拟化加密磁盘密钥设置方案浅析 — TKS1提到的
AF-splitted encrypted master key,LUKS可以支持多个用户密码,每个密码可以唯一解密一个key master,从上图我们看到有8个key slot,对应地可以存放8个KM,因此LUKS支持用户最多设置8个密码。 - bulk data: 磁盘密文数据
- 围绕整体格式示意图,我们重点分析LUKS规范加密磁盘的头部格式。
头部格式
- 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在磁盘的偏移地址。
PBKDF2字段
- 在LUKS规范中,PBKDF2被用在了两个地方,一个作用在用户密码的派生密钥计算中,一个是作用在master key摘要计算中,因此会有两份PBKDF2的参数,一份存在于公共头部,用于计算master key摘要;一份存在于key slot中,用于计算用户密码的派生密钥。
- 另外公共头部中还有hash spec字段与PBKDF2相关,它指定了PBKDF2在迭代时使用的具体hash算法,两处使用PBKDF2的场景都会使用该字段指定的hash算法,其可选值如下:

其它字段
- magic,6字节,标识磁盘为LUKS的魔数,其值为{‘L’,’U’,’K’,’S”,0xBA,0XBE}
- version,2字节,LUKS版本号
- payload-offset: 加密数据的起始偏移
- key-bytes: master key的长度
- uuid: 磁盘uuid
- ks.active: key slot中的active字段,标识该key slot关联的Key Material是否使能,使能则存放有
AF-splitted encrypted master key - ks.key-material-offset: key slot中的key-material-offset字段,存放Key Material的偏移地址
格式验证
LUKS加密盘
qemu-img create --object secret,id=sec0,data=abc123 -f luks -o cipher-alg=aes-256,cipher-mode=xts,ivgen-alg=plain64 -o key-secret=sec0 hyman_100G.luks 100G
# qemu-img info hyman_100G.luks
image: hyman_100G.luks
file format: luks
virtual size: 100G (107374182400 bytes)
disk size: 256K
encrypted: yes
Format specific information:
ivgen alg: plain64
hash alg: sha256
cipher alg: aes-256
uuid: 318622eb-49f9-4d26-815c-a623a67b2ad9
cipher mode: xts
slots:
[0]:
active: true
iters: 122726
key offset: 4096
stripes: 4000
[1]:
active: false
key offset: 262144
[2]:
active: false
key offset: 520192
[3]:
active: false
key offset: 778240
[4]:
active: false
key offset: 1036288
[5]:
active: false
key offset: 1294336
[6]:
active: false
key offset: 1552384
[7]:
active: false
key offset: 1810432
payload offset: 2068480
master key iters: 31665
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- LUKS加密盘格式如下:

- 对照加密盘格式分析磁盘头部如下:

Qcow2 LUKS加密盘
qemu-img create --object secret,id=sec0,data=abc123 -f qcow2 -o encrypt.format=luks,encrypt.key-secret=sec0 hyman_100G_encrypted.qcow2 100G
# qemu-img info hyman_100G_encrypted.qcow2
image: hyman_100G_encrypted.qcow2
file format: qcow2
virtual size: 100G (107374182400 bytes)
disk size: 480K
encrypted: yes
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
encrypt:
ivgen alg: plain64
hash alg: sha256
cipher alg: aes-256
uuid: 75f409ef-96e2-4de0-8246-09d6699f717d
format: luks
cipher mode: xts
slots:
[0]:
active: true
iters: 113250
key offset: 4096
stripes: 4000
[1]:
active: false
key offset: 262144
[2]:
active: false
key offset: 520192
[3]:
active: false
key offset: 778240
[4]:
active: false
key offset: 1036288
[5]:
active: false
key offset: 1294336
[6]:
active: false
key offset: 1552384
[7]:
active: false
key offset: 1810432
payload offset: 2068480
master key iters: 29954
corrupt: false
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 要实现luks加密盘,需要有地方存放luks加密盘的头部数据,对于luks加密磁盘,QEMU可以创建一个稀疏文件后直接以luks格式化该文件头部,后续按照LUKS规范设置密钥并加解密数据,实现luks加密盘。
- 对于Qcow2加密磁盘,QEMU的实现是将LUKS头部作为Qcow2磁盘元数据的一部分,即扩展Qcow2的头部,让其可以支持存放LUKS的头部,当Qcow2读写数据时,改变其原有流程,让其写入cluster前先将数据加密再写入,读cluster后将数据解密后再返回。这样即可支持Qcow2磁盘的luks加密。
- Qcow2在具体支持luks磁盘加密时,将其扩展头部用于存放luks的头部,格式如下图所示:

- 对照上述格式分析Qcow2加密磁盘头部如下:
