用LUKS为云盘敏感数据上锁

如何妥善地解决块存储的安全问题?这篇教程我们将一起探索在腾讯云上,为云硬盘做基于dm-crypto/LUKS的块设备加密的方法实践。

0x01 静止数据(data-at-rest)加密分层

静止数据安全 VS. 在途数据安全

静止数据(data-at-rest)加密确保文件始终以加密形式存储在磁盘上。只有在系统运行和受信任的用户解锁时,文件才会以可读的形式对操作系统和应用程序开放,此时转变为在途数据(data-in-use或data-in-transit)。而未经授权直接查看磁盘内容,只能得到无意义的随机数据,而不是实际的文件。

静止数据加密可以在以下场景保护数据不泄露:如非受信任的人可能进入机房、硬盘丢失或被盗,如笔记本电脑、上网本或外部存储设备、在修理厂修理、以及硬盘弃用后。简单点,硬盘丢了也不担心泄密

当然,静止数据加密也不是万能的,它无法在以下场景下保证数据安全:运行时网络侵入,攻击者可以通过互联网在系统运行时并已经解锁了磁盘的加密部分后侵入系统;运行时物理侵入,在机器开机时,对机器进行获得物理访问权;被胁迫交出密码等。所以,完整的数据安全策略应该包括静止和在途的数据的加密。而对于途数据的安全就讨论更多了,很多网络安全相关协议,如最典型的TLS等等。

静止数据加密的分层模型及场景对比

正如大家熟知的网络协议的TCP/IP四层模型,各层有不同的安全方案,如传输层有TLS,网络层有IPSec等。数据存储的也在各个层面上有各自的安全加密实现,操作系统的存储栈如下图:

用LUKS为云盘敏感数据上锁

最上层的应用层通常可以由具体软件商自己定制的库实现,一般来说,越上层开发越灵活,但是应用层的加密对开发者其实非常不友好:第一是因为特定加密算法的细节(密钥、摘要生成等)毕竟距离业务太远,第二是应用级别的加密无法利用系统级别的缓存,如页缓存等,想获得理想性能的实现难度非常大。所以更适合更底层的抽象,如用户态加密库OpenSSL、PGP等。最底层的硬件层加密是指基于硬件实现的全盘加密(Hardware-based FDE, Full Disk Encryption),如SED(Self-encrypting Drives)或Opal兼容设备等。本节不对其做重点分析,而主要分析中间两类OS层的数据加密实现:栈式文件系统加密(stacked filesystem encryption)块设备加密(block device encryption)

文件系统级加密使得数据加密对应用程序是透明的,因为文件系统本身就会对数据进行加/解密,然后再将数据传递给块子系统,所以无论应用程序是否有加密支持,文件都会被加密。其灵活性还体现在,可以配置成只对某一特定目录进行加密,或者对不同的文件有不同的密钥。然而,这种灵活性是以更复杂的配置为代价的。文件系统加密通常不如块设备加密安全,因为大部分的栈式文件系统只能加密文件的内容,还做不到加密相关的元数据,如文件大小、文件数量、目录树布局等,这些对潜在的攻击者来说仍是可见的。而栈式(stacked)是指这类加密工作是在已存在的文件系统(如Ext4/Btrfs)上再加一层,在已有文件系统的基础之上再进行其中数据的加密,如相对传统(有些缺陷的)的eCryptfs、EncFS和新近的gocryptfs及CryFS等,而且其中大部分是通过FUSE技术实现。 栈式加密文件系统是目前文件系统级加密的主流实现方法。

而相对更下层的块设备加密(也称磁盘卷加密或全盘加密)也使得数据加密对应用程序甚至整个文件系统都是透明的。与文件系统层加密不同的是,它对块设备上的所有数据进行加密,包括文件元数据、可用空间、硬盘分区表信息甚至作为loop device的文件都可以。块设备加密性能通常非常高,也更安全。不过,它的灵活性较前者受限,通常是对整个块设备进行加密,所以没有按目录、按文件或按用户的配置。

选择哪一层加密取决于实际情况,应用程序和文件系统级别的加密通常是客户端系统的首选,因为它具有灵活性。家用的多用户桌面上的每个用户可能希望用自己的密钥加密他们的主目录,而不对一些共享目录进行加密。相反,在服务器系统上,尤其是云服务场景下,则推荐用启用块设备加密。毕竟,我们所有的数据都必须得到保护,因为没有例外或覆盖,不太需要上层提供的选择性灵活性。

0x02 块设备加密原理:dm-crypt/LUKS

dm-crypt

dm-crypt是Linux内核提供的标准的设备映射加密(device-mapper encryption)功能,在Linux2.6版本作为透明的硬盘加密子系统加入内核,用于控制分区和密钥管理。它是以device mapper target的方式实现的,意味着它可以作为块设备用来支持文件系统、swap分区,或作为LVM。它同时可以加密全盘、可插拔设备、硬盘分区、软RAID、逻辑卷以及文件(通过loop device),总之其非常灵活。

Linux下的设备映射,device mapping,可以简单地理解为通过它把物理设备和逻辑设备间的映射关系,把物理设备(如硬盘卷设备等)进行了资源池化(虚拟化),典型应用是LVM逻辑卷管理器。

cryptsetup

dm-crypt作为device mapper target,全部在内核中的,只负责块设备的加解密。它依靠用户态的front-ends工具cryptsetup来协助进行创建加密卷、管理密钥授权等工作。cryptsetup可以用于以下类型的块设备加密:LUKS (默认,即LUKS卷)、plain(普通的dm-crypt卷)、以及loopAES 和 Truecrypt(有限支持)。

LUKS

LUKS (Linux Unified Key Setup),2004年的Linux硬盘加密标准,其规定了各种硬盘加密软件的密钥管理等功能的兼容实现接口。标准就是基于dm-crypt/cryptsetup改良提出的,且后者就是LUKS的标准参考实现。cryptsetup默认使用的一个实现LUKS标准的额外封装层,它将dm-crypt所需的所有设置信息存储在磁盘本身,并抽象了分区和密钥管理,以提高易用性和加密安全性。普通的dm-crypt模式,是原始的内核功能,没有LUKS层的封装,用它来应用同样的加密强度是比较困难的,现在已不推荐使用。所以,dm-crypt/LUKS已是Linux块设备加密的唯一事实标准。

dm-crypt加解密流程与算法

如下图,dm-crypt并不直接进行真正的加解密的计算工作,而是通过Linux Kernel Crypto API来异步地完成。对数据的读写请求在从文件系统(filesystem)到块设备驱动(block device driver)之间,其实是由dm-crypt和crypto-api两个模块相互协作完成。

用LUKS为云盘敏感数据上锁
dm-crypto与crypto-api协作加密流程

具体细节简要分析:

对于文件系统的写请求,dm-crypt并不会立即处理,而是将其转成加密的任务请求放入一个名为kcryptd的内核工作队列中,以便在以后方便的时候进行。当时间到了,dm-crypt就会将这些加密请求发送到Linux Crypto API进行实际加密。而现代的 Linux Crypto API 也是异步的,所以取决于系统将使用哪种特定的实现,很可能不会立即处理,而是再次排队等待以后合适的时间。当Linux Crypto API最终将进行加密时,dm-crypt会尝试通过将每个请求放入红黑树来对待处理的写请求进行排序。然后,再在稍后合适时间又有一个单独的内核线程,实际上是把树上的所有IO请求都取下来,然后把它们继续下放到设备驱动。

对于读请求,需要先从硬件上获取加密数据,而dm-crypt并不是直接请求设备驱动,而是将请求排入一个名为 kcryptd_io的不同工作队列。在之后的某个时间点,获取到加密的数据时,还是通过kcryptd工作队列安排它进行解密。kcryptd将把解密的请求发送到Linux Crypto API,后者再异步解密数据返回文件系统。

下图为dm-crypt支持AES的XTS加密模式(XEX Tweak with Ciphertext Stealing),其是目前安全性最高的AES加密模式。

用LUKS为云盘敏感数据上锁
AES的XTS加密模式示意

dm-crypt/LUKS默认使用的AES-XTS是目前最快的算法,因为基于AES(高级加密标准算法 Advanced Encryption Standard)算法可以获得硬件指令集支持的加速,在x86体系下由AESNI(Intel® AES New Instructions Set)实现,其极大地提升了CPU加解密的过程。可以通过lscpu | grep aes验证服务器的CPU是否支持aes指令加速。

0x03 块设备加密实践:cryptsetup

初步理解完Linux下块设备加密的概念原理,接下来我们开始实践感受一下。

我们只需要一个工具:cryptsetup,它是dm-crypt(及其LUKS扩展)的“前端”,用来在方便管理员在用户态完成dm-crypt/LUKS加密设备的管理工作。注意,cryptsetup虽然也可以管理plain dm-crypt设备卷,但并不推荐,因为LUKS加密扩展的安全性更高。我们接下来将通过cryptsetup工具完成LUKS加密卷的格式化、打开以及密钥管理等工作。cryptsetup工具使用格式如下:

cryptsetup <options> <action> <action args>

cryptsetup的具体功能通过其下的子命令(action)来完成,子命令区分严格大小写,操作LUKS加密卷的子命令有前缀luks,如luksDump等。

本文后续的操作环境为:腾讯云CVM云服务器,S5标准型,Debian 10(Buster) stable 操作系统, cryptsetup版本为2.1.0。

1、创建LUKS卷

我们通过luksFormat子命令完成LUKS加密卷的创建(格式化)。子命令后直接跟设备名称即可:

cryptsetup luksFormat /dev/vdb

格式化过程也是非常快的,实测530GiB是秒级完成,运行结果如下图:

用LUKS为云盘敏感数据上锁
创建LUKS卷

在创建过程中,需要手动输入大写的YES,并输入两次密码,即设置LUKS卷的初始KEY。完成后,我们可以通过fdisk查看目标卷,将看到提示crypto_LUKS签名已经存在,验证格式化完成。

作为补充,当前Format子命令默认加密参数为:-c aes-xts-plain64 -h sha512 -s 512,这些参数目前均是相对最优的选择(见后查看Header信息以及cryptsetup benchmark结果),使用暂时无需太过关注。

2、查看LUKS卷Header信息

创建LUKS卷后,我们可以通过luksDump子命令查看Header信息,如版本、元数据大小,加密算法以及Keyslot(所存的密码)的列表信息等等,如图:

用LUKS为云盘敏感数据上锁
查看LUKS卷头元信息

另外,通过status子命令也可以查看当前LUKS加密卷的状态信息:

root@VM-2-9-debian:~# cryptsetup status /dev/mapper/MyEncryptedDrive 
/dev/mapper/MyEncryptedDrive is active and is in use.
  type:    LUKS2
  cipher:  aes-xts-plain64
  keysize: 512 bits
  key location: keyring
  device:  /dev/vdb
  sector size:  512
  offset:  32768 sectors
  size:    1111457792 sectors
  mode:    read/write

3、备份恢复LUKS卷Header

如前所述,LUKS卷有一个存放元数据的卷头Header。这里个是相对脆弱的部分,如果有损坏,将导致整个卷的无法访问,且不能通过fsck等文件系统check工具完成恢复。所以推荐在创建后,对Header进行备份操作。Header的备份与恢复通过luksHeaderBackupluksHeaderRestore完成,如:

cryptsetup luksHeaderBackup /dev/vdb --header-backup-file LuksHeader.bin
# 此命令会在当前目录下生成LuksHeader.bin文件用于卷头部元数据备份
​
cryptsetup luksHeaderRestore /dev/vdb --header-backup-file LuksHeader.bin
# 此命令读取当前目录下的备份文件恢复LUKS卷头部元数据

4、解锁使用LUKS卷

解锁LUSK卷通过luksOpen子命令完成,需要指定设备映射的文件名,命令格式如下:

cryptsetup luksOpen /dev/vdb MyEncryptedDrive
用LUKS为云盘敏感数据上锁
解锁LUKS卷

执行时需要输入密码,成功后可以在/dev/mapper/目录下看到刚指定名称(本例子中是MyEncryptedDrive)的设备文件,即解锁成功,后续我们可以操作该设备/dev/mapper/MyEncryptedDrive了。

后续如有需要,也可以随时关闭块设备(硬盘卷),即再次上锁,通过luksClose即可:

cryptsetup luksClose MyEncryptedDrive

使用上,对于解锁后的device-mapper卷,我们完全可以把它看成普通的卷一样。可以在其上mkfs(创建格式化文件系统)、fsck(执行文件系统检查)、mount(挂载)、通过df查看用量,用法与一般的/dev/vdx设备没有任何区别。

用LUKS为云盘敏感数据上锁
像普通卷一样操作LUKS加密卷

这就是内核中实现的dm-crypt的重要优势,它将块设备的加密过程封装在比文件系统层更底层的block抽象层,对文件系统及其更上层的用户态应用完全做到透明无感知。

如挂载过程中,不是挂载一般的块设备分区文件/dev/vdbx,而是挂载新生成的device-mapper设备映射文件/dev/mapper/xxx:

mount /dev/mapper/MyEncryptedDrive /data/disk-1

另外,如果手误执行挂载了加密前的原设备呢?不用担心,mount会提示以下错误:

root@VM-2-9-debian:~# mount /dev/vdb /data/disk-1
mount: /data/disk-1: unknown filesystem type 'crypto_LUKS'.

mount命令表示不认识这个名为crypto_LUKS的文件系统,即LUKS格式化后的块设备(硬盘卷)是不能执行挂载的。

那么,什么时候需要手动输入硬盘的密码呢?挂载前解锁时最常见的场合,通常服务器重启后,均需要手动输入密码。可以通过编辑/etc/crypttab文件来设置重启自动解锁(auto decrypt)硬盘,对于信任的计算资源,如完善了一定安全策略的云服务器等,其实是完全可以的,因为有的时候效率和操作便捷性的优先级也很高。

5、添加/变更LUKS卷密码

LUKS支持多个密码(Keyslot),执行管理操作(如解锁、变更密码)时,只需要输入任意其中一个即可完成授权。可以通过luksAddKeyluksChangeKeyluksDeleteKey来完成密码的添加、变更和删除操作,很方便。

不过注意,最后一个密码删除后,整个LUKS卷就无法操作了,一定要留一个至少。删除时,会有强提示:

用LUKS为云盘敏感数据上锁
删除最后一个密钥的提示

这里的最佳实践是:如果要更改KEY,最好先加Key,再删除原有的Key,比较保险。一旦全部误删了,用之前备份的Header文件恢复即可。

至此,我们完成介绍了LUKS加密块设备的常见操作,后面我们看看LUKS的性能。

0x04 LUKS性能分析与最佳实践

对于上述加密后的硬盘,其访问性能是否会变慢?这几乎每个人关心的问题。下面,我们通过fio工具对两种读写模式——顺序写以及4K随机读——分别进行测试。

顺序写性能测试

fio -name=write-throughput -readwrite=read -blocksize=128k -numjobs=2 -ioengine=libaio -iodepth=1 -direct=1 -runtime=60 -refill_buffers -norandommap -randrepeat=0 -group_reporting --size=10G -filename=/data/disk-1/a

对于Ext4文件系统:

加密前: IOPS=1211, BW=151MiB/s (159MB/s)(9088MiB/60001msec)

加密后: IOPS=1200, BW=150MiB/s (157MB/s)(9002MiB/60001msec)

对于Btrfs文件系统:

加密前:IOPS=1207, BW=151MiB/s (158MB/s)(9054MiB/60001msec)

加密后:IOPS=1206, BW=151MiB/s (158MB/s)(9047MiB/60002msec)

注:以上测试的时延Latency结果也相近。

4K随机读性能测试

fio -name=read-latency-iops -readwrite=randread -blocksize=4k -numjobs=2 -ioengine=libaio -iodepth=1 -direct=1 -runtime=60 -refill_buffers -norandommap -randrepeat=0 -group_reporting --size=10G -filename=/data/disk-1/a

对于Ext4文件系统:

加密前:IOPS=3730, BW=14.6MiB/s (15.3MB/s)(874MiB/60001msec)

加密后:IOPS=3614, BW=14.1MiB/s (14.8MB/s)(847MiB/60001msec)

对于Btrfs文件系统:

加密前:IOPS=3802, BW=14.9MiB/s (15.6MB/s)(891MiB/60001msec)

加密后:IOPS=3721, BW=14.5MiB/s (15.2MB/s)(872MiB/60001msec)

注:以上测试的时延Latency结果也相近。

加密算法评测

通过cryptsetup benchmark命令可以在评测服务器上各种加密(hash/摘要)算法的性能,大致结果如下:

用LUKS为云盘敏感数据上锁
各个加密算法的评测

可以看到,Key长度(key-size)为512时的aes-xts(cipher)以及pbkdf2-sha256(hash)是相对性能最佳的选择,也是当前的默认。另外注意,这个评测是在内存中进行的,数值仅能用于各个算法的相对比较,和真实的块设备读写时还是相差不少的。

性能分析小结

腾讯云的高性能云硬盘,对其进行LUKS加密访问,其性能在加密前后总体相差无几,加密后性能(IOPS、带宽等)相较加密前略低一点(1%~3%以内),且均超过云硬盘官网标称性能。另外,其性能受文件系统影响不大。Linux下的dm-crypt/LUKS是个极其高性能的加密实现方案。

最佳实践补充

尽量只加密数据盘,系统盘一般无需加密,至少/boot分区可以不加密。因为全盘加密的话,每次VM重启的时候也要进入终端(只能VNC)输入密码,实践中很不方便,至少/boot所在的分区可以不加密,这也是某种意义上的计算存储分离权限。

LUKS一般在LVM(卷管理软件)之下,及LVM on LUKS,先对所有的盘加密,再通过LVM统一管理各个加密卷,这是最简单直接的方案。但其不能加密LVM卷的管理信息,如果有更加灵活的需求也可以选择LUKS on LVM,视具体情况而定。

0x05 总结

至此通过本文,相信你已经已经初步了解了如何对静止数据进行加密保护的分类、块设备加密的概念原理以及当前的标准方案dm-crypt/LUKS、通过cryptsetup工具实践体验了硬盘加密的详细过程,以及掌握了评测性能的思路与方法,是否收获满满呢?

那么,你的(云)硬盘是否加密了呢,快去为它加一把安全的锁吧~

0x06 参考资料

本文来自腾讯云计算社区,转载请注明出处:https://computeinit.com/archives/2884

发表评论

登录后才能评论
交流群