LUKS磁盘格式
1 介绍
LUKS是"Linux Unified Key Setup"的简写。目前qemu的磁盘加密格式是luks格式。
LUKS磁盘格式如下图所示:
- LUKS phdr: LUKS partition header。包含了使用的加密算法,加密模式,密钥长度,UUID和Master Key的校验和。
- KM1~KM8: Key Material 1~8。
- bulk data: 由Master Key加密的数据块。
另外,LUKS phdr中还包含了Key Slot的信息。每个Key Slot和一个KM相关联。Key Slot处于激活状态时,其对应的KM中保存了Master Key的加密副本(该加密副本由用户密码锁定)。Master Key则用于加密数据块。
一个LUKS分区的用户密码数量和Key Slot数量一致。通常情况下Master Key长度是16或者32字节。
2 前言
2.1 数据块加密
LUKS并未限制具体的加密算法实现(如AES或Twofish),加密和解密过程如下图所示:
以下伪代码展示了加解密的过程:
enc-data = encrypt(cipher-name, cipher-mode, key, original, original-length)
original = decrypt(cipher-name, cipher-mode, key, enc-data, original-length)
注意:如果加密算法要求固定块长度,不足的部分需要用0做填充。反之,解密的时候需要将填充的0去除。
2.2 加密哈希
在 PBKDF2 中需要一个伪随机函数,对于 AFsplitting 需要一个扩散函数。因此在HMAC setup中需要用到该哈希函数。
2.3 PBKDF2
LUKS 需要处理来自诸如键盘输入等熵弱来源的密码,PBKDF2(PKCS #5’s password based key derive function 2)正是为增强熵弱密码的安全特性而定义。LUKS默认使用SHA1作为伪随机函数,也可以通过设置hash-spec字段来设置其他哈希函数。
PBKDF2如下图所示:
在伪代码中,使用了以下语法:
result = PBKDF2(password, salt, iteration-count, derived-key-length)
以上PBKDF2函数的输出依赖于实际的hash-spec,可以将hash-spec认为一种环境变量。
有关PBKDF2的其他介绍如下:
- 美国国家标准与技术研究院推荐。
- 可以通过调整key来扩展,从而避免暴力破解。通过key扩展的基本思路是,在将密码哈希后,再使用key加上哈希值再使用相同的算法进行多次的哈希。如果黑客尝试去破解的话,他会因此多花费几十亿次计算的时间。PBKDF2可以通过指定迭代次数,你想让他多慢,他就有多慢。
- 通过加盐的方式预防彩虹表的破解方式。盐是一个添加到用户的密码哈希过程中的一段随机序列。这个机制能够防止通过预先计算结果的彩虹表破解。每个用户都有自己的盐,这样的结果就是即使用户的密码相同,通过加盐后哈希值也将不同。然而,在将盐与密文存储的位置上有很多矛盾的地方,有的时候将两者存在一起比较方便,有的时候为了安全考虑又不得不将两者分开存储。
- 不需要额外的库或者工具,这是一个开源的实现,在工作环境中能很方便的使用。
2.4 AF-Splitter
LUKS使用了反取证(Anti-Forensic)信息拆分。参考实现使用了SHA1作为底层扩散函数。
split-material = AFsplit(unsplit-material, length, stripes)
unsplit-material = AFmerge(split-material, length, stripes)
注意:split-material的长度是unsplit-material的stripes倍,即length * stripes字节。length是unsplit-material的字节数。
用符号D代表unsplit-material,H代表扩散函数,n表示条带数量,AFsplit函数返回的值为s1, s2 . . . sn。其中s1 . . . sn−1是随机选取的,sn则是通过以下方式计算得出:
- d0 = 0
- dk = H(dk−1 ⊕ sk)
- sn = dn−1 ⊕ D
反之,AFmerge则计算dn−1和D:
D = dn−1 ⊕ sn
3 LUKS partition header
LUKS partition header总长度为592字节,其定义如下:
偏移 | 字段名称 | 长度 | 数据类型 | 描述 |
---|---|---|---|---|
0 | magic | 6 | byte[] | 魔数,值为LUKS_MAGIC |
6 | version | 2 | uint16_t | LUKS版本,目前是1 |
8 | cipher-name | 32 | char[] | 加密算法 |
40 | cipher-mode | 32 | char[] | 加密模式 |
72 | hash-spec | 32 | char[] | 哈希算法 |
104 | payload-offset | 4 | uint32_t | 数据块偏移,单位是sector,即512字节 |
108 | key-bytes | 4 | uint32_t | Master Key长度,单位字节 |
112 | mk-digest | 20 | byte[] | Master Key PBKDF2计算的校验和 |
132 | mk-digest-salt | 32 | byte[] | 盐值,Master Key PBKDF2计算时的参数 |
164 | mk-digest-iter | 4 | uint32_t | 迭代次数,Master Key PBKDF2计算时的参数 |
168 | uuid | 40 | char[] | 分区UUID |
208 | key-slot-1 | 48 | key slot | key slot 1 |
256 | key-slot-2 | 48 | key slot | key slot 2 |
... | ... | ... | ... | ... |
544 | key-slot-8 | 48 | key slot | key slot 8 |
key slot定义如下:
偏移 | 字段名称 | 长度 | 数据类型 | 描述 |
---|---|---|---|---|
0 | active | 4 | uint32_t | key slot状态,enabled/disabled |
4 | iterations | 4 | uint32_t | 迭代次数,PBKDF2计算时的参数 |
8 | salt | 32 | byte[] | 盐值,PBKDF2计算时的参数 |
40 | key-material-offset | 4 | uint32_t | key material偏移,单位是sector,即512字节 |
44 | stripes | 4 | uint32_t | 反取证(Anti-Forensic)条带数量 |
4 LUKS操作
4.1 初始化
LUKS初始化需要以下参数:
- Master Key,用于数据块加密
- 加密算法+加密模式
整个磁盘的布局取决于phdr后面的Key Material区域的长度。phdr是固定长度的,而Key Material区域的长度取决于Master Key的长度和AF-splitter用到的条带数量。计算好Key Material区域的长度之后,phdr中的payload-offset字段需要填入。
Master Key的校验和需要通过PBKDF2进行计算,其中会涉及多次加盐和迭代哈希。
除此之外,初始化阶段还需要激活至少一个Key Slot。
初始化的伪代码如下:
masterKeyLength = defined by user
masterKey = generate random vector, length : masterKeyLength
phdr.magic = LUKS_MAGIC
phdr.version = 1
phdr.cipher−name = as supplied by user
phdr.cipher−mode = as supplied by user
phdr.key−bytes = masterKey
phdr.mk−digest−salt = generate random vector, length : LUKS_SALTSIZE
// benchmarked according to user input
// ( in older versions fixed to 10 )
phdr.mk−digest-iteration−count = as above
phdr.mk−digest = PBKDF2(masterKey,
phdr.mk−digest−salt,
phdr.mk−digest−iteration-count,
LUKS_DIGESTSIZE)
stripes = LUKS_STRIPES or user defined
// integer divisions, result rounded down
baseOffset = ( size of phdr ) / LUKS_SECTOR_SIZE + 1
keyMaterialSectors = ( stripes ∗ masterKeyLength ) /
LUKS_SECTOR_SIZE + 1
for each keyslot in phdr as ks {
// Align keyslot up to multiple of LUKS_ALIGN_KEYSLOTS
baseOffset = round_up ( baseOffset , LUKS_ALIGN_KEYSLOTS)
ks.active = LUKS_KEY_DISABLED
ks.stripes = stripes
ks.key−material-offset = baseOffset
baseOffset = baseOffset + keyMaterialSectors
}
phdr.payload−offset = baseOffset
phdr.uuid = generate uuid
write phdr to disk
4.2 密钥创建
密钥创建的伪代码如下:
masterKey = must be available, either because it is still in
memory from initialisation or because it has been
recovered by a correct password
masterKeyLength = phdr.key−bytes
emptyKeySlotIndex = find inactive key slot index in phdr by
scanning the keyslot.active field for
LUKS_KEY_DISABLED.
keyslot ks = phdr.keyslots[ emptyKeySlotIndex ]
PBKDF2-IterationsPerSecond = benchmark system
ks.iteration-count = PBKDF2-IterationsPerSecond *
intendedPasswordCheckingTime (in seconds)
ks.salt = generate random vector, length: LUKS_SALTSIZE
splitKeyLength = masterKeyLength * ks.stripes
pwd = read password from user input
pwd−PBKDF2ed = PBKDF2( password ,
ks.salt,
ks.iteration-count,
masterKeyLength) // key size is the same
// as for the bulk data
encryptedKey = encrypt(phdr.cipher-name,
phdr.cipher-mode,
pwd-PBKDF2ed, // key
splitKey, // content
splitKeyLength) // content length
write to partition(encryptedKey,
ks.key-material-offset,
splitKeyLength)
ks.active = LUKS_KEY_ACTIVE
update keyslot ks i phdr
密钥创建的过程如下图所示:
4.3 Master Key恢复
Master Key恢复的伪代码如下:
read phdr from disk
check for correct LUKS_MAGIC and compatible version number
masterKeyLength = phdr.key-bytes
pwd = read password form user input
for each active keyslot in phdr do as ks {
pwd-PBKDF2ed = PBKDF2(pwd, ks.salt, ks.iteration-count,
masterKeyLength)
read from partition(encryptedKey, // destination
ks.key-material-offset, // sector number
masterKeyLength * ks.stripes) //number of bytes
splitKey = decrypt(phdr.cipherSpec, // cipher spec
pwd-PBKDF2ed, // key
encryptedKey, // content
encrypted) // content length
masterKeyCandidate = AFmerge(splitKey, masterkeyLength, ks.stripes)
MKCandidate-PBKDF2ed = PBKDF2(masterKeyCandidate,
phdr.mk-digest-salt,
phdr.mk-digest-iter,
LUKS_DIGEST_SIZE)
if equal (MKCandidate-PBKDF2ed, phdr.mk-digest) {
break loop and return masterKeyCandidate as correct master key
}
}
return error, password does not match any keyslot
Master Key恢复过程如下图所示:
5 常量
符号 | 值 | 描述 |
---|---|---|
LUKS_MAGIC | LUKS魔数 | |
LUKS_DIGESTSIZE | 20 | Master Key校验和长度 |
LUKS_SALTSIZE | 32 | PBKDF2盐值长度 |
LUKS_NUMKEYS | 8 | Key Slot数量 |
LUKS_KEY_DISABLED | 0x0000DEAD | Key Slot disable标识 |
LUKS_KEY_ENABLED | 0x00AC71F3 | Key Slot enable标识 |
LUKS_STRIPES | 4000 | AFsplit条带数量 |
LUKS_ALIGN_KEYSLOTS | 4096 | 默认Key Slot对齐边界 |
LUKS_SECTOR_SIZE | 512 | sector大小 |
附 可用的加密算法和哈希算法
可用的加密算法包括:
-
aes
-
twofish
-
serpent
-
cast5
-
cast6
可用的加密模式包括: -
ecb
-
cbc-plain
-
cbc-essiv:hash
-
xts-plain64
可用的哈希算法 -
sha1
-
sha256
-
sha512
-
ripemd160